18

The notion of multiple processes has been around a long time, at least since the IBM 360. Multiple processes running at the same time, in separate memory spaces with protection from each other.

(In this context, I'm not talking about the distinction between one or several physical CPU cores, so 'at the same time' can equally well mean really physically at the same time, or with preemptive multitasking that makes it look like at the same time.)

Threads differ from processes in that they run in the same memory space, they have mutable shared memory, so if thread A pokes a value into a memory location, thread B reading that memory location a few microseconds later, expects to see the value A just put there.

Now, the Amiga had preemptive multitasking with no memory protection, so tasks on that machine could do the above, but it's not exactly what you were supposed to do. You were supposed to send a message from one task to the other, with a pointer to the block of memory that was being transferred. At the hardware level, nothing special was happening, but you were at least theoretically supposed to program according to that logic, with a view to later versions of the machine adding an MMU. (Which didn't actually happen, partly because by the time the 68030 with its built-in MMU came along, too much software had already been written that ignored the rules and would break, and partly because by that time, Commodore R&D was being mismanaged into the ground, but I digress.)

I'm not talking about that. Not talking about 'this machine doesn't have an MMU so you can get away with poking anything you want wherever you want'.

I'm talking about 'thread A pokes a value into their shared memory space, thread B expects to read that value a few microseconds later without explicitly passing a message or copying or even transferring ownership of a block of memory' as a supported API, something you are supposed to do.

(In a single-core system, the only difference is what the documentation says about what will be supported in the future. In a multicore system, mutable shared memory of this kind requires special hardware support for cache coherence. Different CPU architectures nominally have different models in terms of exactly how strongly they support various versions of this, but in practice, once you allow mutable shared memory, everything is under pressure to converge on the strongest model because otherwise you get Heisenbugs showing up on your platform and not on your competitors', which is the worst case scenario, e.g. Apple M1 has an optional TSO mode for x86 emulation; I predict in a few years it will just go TSO all the time and eat the efficiency cost.)

By that definition, when did the first system add explicit support for threads? I vaguely remember some time in the early nineties, Windows NT added it, the various UNIX vendors spent a while arguing they were a bad idea (a position I happen to agree with, but that's another matter) before being reluctantly forced to follow suit. (Why? As a marketing bullet point, or because cross-platform software like Oracle was being rewritten to assume threads? If the latter, why was it? If because it performed better that way, why did it?) But that's a vague memory, and doesn't preclude the possibility that – as very often turns out to be the case – mainframes had already done it a couple of decades earlier.

17
  • 4
    > 'thread A pokes a value into their shared memory space, thread B expects to read that value a few microseconds later without explicitly passing a message or copying or even transferring ownership of a block of memory' This will work safely only in the most trivial cases. To avoid bugs most of the time you'd need to explicitly use various synchronization primitives (e.g. mutexes in most languages or something like channels in Go) , though incorrect use of those primitives can lead to bugs of its own.
    – moonwalker
    Feb 11 at 17:30
  • 7
    You got that history the wrong way round. Threads occured first on Unix, notably Sun Solaris (as Lightweight Processes, LWP), before Windows even knew what that might be.
    – tofro
    Feb 11 at 18:01
  • 3
    @SolomonSlow Yep. Or put another way: it's easy to read the source code of a multithreaded program and think we understand it. But reaching a high degree of assurance that our understanding matches what's really going on, that there are no lurking heisenbugs? That's another order of difficulty altogether.
    – rwallace
    Feb 11 at 20:25
  • 8
    "but I digress" Don't worry, this is RC.SE. Digression is pretty much welcomed - as long as it's an interesting digression.
    – Graham Nye
    Feb 11 at 20:34
  • 3
    @RossPresser Total Store Ordering. A way of using memory (i.e. store) that is similar to that used by a x86 processor thus making x86 emulation faster on Apple's M1 (ARM) processor. (reference)
    – Graham Nye
    Feb 12 at 19:12

11 Answers 11

20

As a matter of fact, IBM did introduce support not only for multiple parallel processes, but also for multi-threading (called "sub-tasks") in their Multiprogramming with a Variable number of Tasks (MVT) variant of OS/360. That is the earliest mention of multi-threading concept in a production system I could find. MVT variant was announced AFAIK some time in 1964, but didn't become available to the customers until 1967, at the same time as MFT variant that provided the same capabilities but on a smaller scale.

Unfortunately, I am unable to find any information on their thread safety, but considering it was implementing preemptive multitasking, meaning thread could easily be interrupted in a middle of shared memory modification (if it takes more than one CPU instruction), I think it's safe to assume there were synchronization primitives like mutexes or semaphores or some message passing mechanism used in some shape or form.

6
  • 2
    Your OS/360 answer parallels mine for 1900 exec, and at about the same time. Feb 11 at 18:05
  • 2
    @another-dave I swear this is an independent development, I did not reverse-engineer your answer :)
    – moonwalker
    Feb 11 at 18:12
  • 1
    By "at the same time" I meant the same period of OS history, not time of writing RC answers! Feb 11 at 20:15
  • Well, after all, parallel processing in whatever configuration is a great way to utilize a CPU fully - and before it was as formalized as today, many found their way to do it. And adding multiple execution points in a single system need way less work than really separated processes, don't they?
    – Raffzahn
    Feb 11 at 20:18
  • @another-dave I know, but then there would be no joke to make.
    – moonwalker
    Feb 11 at 22:38
17

Any operating system that supports multiple execution contexts within a shared address space has "threads", even if they don't call it that. Because that's all that threads are.

For example, the exec on many ICL 1900s systems supported what they called "subprogramming", in which a program could start an independently-executing entity in its own address space. This was typically done for asynchronous execution of part of the program; of course there was only one CPU, so the exec was allocating CPU cycles on a priority basis.

I say that's "threads". The terminology of that time and place scarcely distinguished "program" from "process", so we can't go solely on the names of things.


For myself, I wrote a threaded application program running on VAX/VMS somewhere (I think) in the late 1980s. The OS did not have a threads library, we wrote it ourselves for this one particular app. When it comes down to it, all you need is the ability to assign a new value to the frame pointer register.

As far as the OS was concerned we were a single schedulable entity. Within the process, it was cooperative multitasking in a common address space. Synchronization was achieved by implementation of, I think, condition variables.

This was not particularly ground-breaking. It was then a new programming technique to me personally, but there was no sense that we were inventing anything.

5
  • 1
    This. There is only a difference between "processes" and "threads" when you add some concept of separate memory spaces. That's a relatively new concept, and certainly one which earlier hardware and OSes didn't have.
    – Graham
    Feb 12 at 15:11
  • True, though I'd still conceptually separate the idea of threads (that are implicitly aware of being threads) from the idea of independent programs executing in a shared address space because the OS/hardware only has one address space. Feb 12 at 16:21
  • VMS supported Asynchronous System Traps (ASTs) which interrupt a running process to invoke a routine in the process's address space. It also supported mapping global sections allowing sharing memory between processes. Both ASTs and shared memory were features of RSX-11 which dates back to 1972.
    – HABO
    Feb 14 at 15:34
  • Of course, and many of my programs did all their work at AST level. But I would not call that threading, because there's only one AST level. It's not so different to Unix signals but done right. It fit well in 11M and VMS because long-running system calls (like QIO$) were fundamentally asynchronous in nature. Feb 14 at 15:38
  • Btw, RSX-11 (in its various implementations) got it from RSX-15. If you read RSX-15 specs, you can see all the paraphernalia of queued I/O, ASTs, event flags... Feb 14 at 15:39
14

Ah, memories of Computer Science 412.

In the Digital Research family of operating systems, MP/M II (1981, possibly earlier variants, but this is what I found quickly) as noted in the programmer's guide already allowed for processes to create sub-processes, had mutual exclusion queues/semaphores, etc. Section 1.2.2, Queue Management, gives an overview of how multiple processes can handle resource access with mutual exclusion queues. Process creation is in Section 3.8, function 144. Queue function details are in Section 3.8, starting with function 134.

Definitely much later than the mainframes, but an example of a relatively early microcomputer implementation. Definitely predates OS/2 and Windows.

Another early microcomputer operating system supporting multiple processes is QNX. Version 1.0 was released in 1984.

1
  • 2
    By adding QNX you may add OS/9 as well :)
    – Raffzahn
    Feb 11 at 20:40
13

I think the Apollo Guidance Computer has a shot at making this claim. It may not be the first, but it's certainly one of the better documented early systems. The AGC had to juggle dozens of things at once. It was the main clock, and the navigation system, it ran the displays, and the radar, and the telemetry uplink, and controlled the engines, all simultaneously on a single processor with about the capabilities of a MOS 6502.

The operating system ("Exec") supported a cooperative multithreaded (or multitasking) scheduler. Tasks were allocated space in memory, set up, and would begin executing, cooperatively passing control back and forth. On top of this, there was also a hard real-time preemptive scheduler, for short and priority jobs. These would normally check conditions and then schedule a long-running task, if appropriate.

It's remarkably sophisticated and modern, very similar to how one might design such a real-time OS today. The book Apollo Guidance Computer: Architecture and Operation by Frank O'Brien, available from the ESA (PDF) goes into quite a bit of detail about the AGC's operating system and software.

12

It wasn't called "threads", but as soon as interrupts were invented - which was in the mid 1950's - which was also before (even primitive) operating systems evolved to manage them - user programs handled them. Which meant ordinary programmers (for the time) were writing concurrent programs "in a single address space" for those computers that had interrupts.

And all the problems you're thinking of w.r.t. concurrency in threads vs address-space-and-other-resource-isolated processes were true for those guys then.

10

Threads seem to have first appeared in IBM's mainframe operating system OS/360 MVT in 1967, although they were called "tasks" at the time.

MP/M (1981) allowed a process to create additional sub-processes which amounted to threads. Since MP/M ran on hardware without memory protection, thread programming would have been somewhat more risky than it is today. Synchronisation was done via message queues.

The first microcomputer operating system that used the name "thread" seems to have been OS/2 1.x; I remember reading about threads for the first time in an article in Dr Dobb’s in 1987 or 1988. The article assumed that the concept was not generally known. Since Windows NT was originally intended to be OS/2 3.x, it had to continue to support the threads of earlier versions of OS/2.

5
  • 1
    @manassehkatz-Moving2Codidact How can an Answer offering valid information be 'not useful', as required for a downvote? The information presented is true and thus quite useful, won't you agree? If you know an earlier occurrence than John, why not adding it in your own answer. No need to downvote (Also, thanks f(serious) or being open about that).
    – Raffzahn
    Feb 11 at 19:09
  • 2
    @manassehkatz-Moving2Codidact I would think a downvote is only useful if a question or answer is straight wrong - or simply spamish crap. To my reading ("seems to have been") this doesn't sound like an absolute claim - but then again, I'm not native English - so not false. Beside the basically fruitless nature of all 'first' discussion, isn't it typical for many RC.SE questions that answers coming up always add new views, like new firsts? I don't think that to make all previous answers bad ones. But given, I hesitate a lot before any downvote - I rather simply ignore less than good answers.
    – Raffzahn
    Feb 11 at 19:34
  • 1
    @manassehkatz-Moving2Codidact No argument about junk (BTW, so far the community seems to handle this quite fine). And I do appreciate reading reasons for downvotes. Still, I do think that downvotes are not meant for to disagree, but only to mark serious wrong/false stuff. Everything else can be handeled by comments - and don't get me wrong, When there is a way to upvote, there as well needs one for downvote - still, the downvote needs way more responsible handling than a twitter style like button.
    – Raffzahn
    Feb 11 at 20:16
  • 1
    @manassehkatz-Moving2Codidact: Added, but a pointer to the documentation you found would help me improve the answer. The MP/M II Programmer's Reference mentions sub-processes once, and has a "thread" field in some data structures, but has no more information. Feb 11 at 20:23
  • It's all in there. I added some section references to my answer. Feb 11 at 20:31
5

The distinction between multithreading and callbacks/interrupts is that a thread has its own execution context, which essentially means its own stack pointer. If you can access the stack pointer and change it around, you have what multithreading takes within the processor's execution paradigm. This holds even for very stack-limited architectures (with an 8-bit stack pointer and/or hardware stack).

I know that back in the 80s I wrote a terminal emulator for a CP/M BIOS (at those time, a rather common coding task) and the parsing of terminal escape sequences (like for cursor positioning) used several subroutines that parsed the sequence so far and then used call termchar to get the next character sent to the terminal. This subroutine swapped out stack pointers back to what the character output system call had been called with last time and returned. When the character output system call was called next time, it swapped stack pointers again and returned to the routine calling termchar.

Now strictly speaking this use of multiple execution contexts was called "coroutines" at that time. The difference to multithreading is that context switching was strictly synchronous and initiated by the currently executing context, of which there was only one at any given point of time. To get to the point where you need more elaborate synchronisation, you either need multiple processors working in the same memory space, or you need a preemptive manner of task switching where control is passed without solicitation.

The original MacOS had cooperative multitasking without preemption. I think that AmigaOS was similar in that regard.

Systems with actual preemption in connection with multithreading are a lot harder to find even though interrupts have been preemptive for a long time. However, letting interrupts maintain their own coroutine for protocol-based or other stateful operation is of course perfectly feasible but not that frequently seen: instead of maintaining protocol state implicitly by the execution context reflected in a saved execution stack, it was a lot more customary (if less effective) to maintain state in some state variable with discrete values and then switch based on its content.

2
  • 1
    I'm glad you mentioned coroutines. That's where I would have gone if I hadn't gone to interrupts. I think they're both examples of "multithreading" within a single address space. And coroutines too are a very early invention - late 1950s apparently.
    – davidbak
    Feb 12 at 18:40
  • 1
    Although there's no reason that interrupts can't have their own call stack, and in some modern machines, they do (e.g., x86, esp. with interrupt gates). (But I think in early computing days, they didn't. But I'm not sure.)
    – davidbak
    Feb 12 at 18:46
3

Despite the elaborate text, there is no clear answerto be given, as this is more about machine capabilities as it is about OS features.

By that definition, when did the first system add explicit support for threads?

Which again would need a definition what an explicit support is. An OS supporting shared memory between processes/tasks/ - as well as code within this shared memory - right away supports what otherwise might be called threads. Even without that term ever mentioning in a manual or sales brochure.

In fact, not even the presence of multiple processes by an OS is required, as long as it allows to receive and produce concurrent data (in/output). Such systems that may be called threaded nowadays.

Example, in the mid to late 70s I could peek into a system running on (*1) BS1000, a real mode DOS like system for /360 class machines. The application was running nominally on two program counters, so call that a process. It was a transaction mode application, so requests that came from terminals (block mode) were dispatched by the communication system to a queue read by the first 'process', switching into a 'thread mode' by selecting a private data set (whooping 256 bytes) tied to that terminal. The request was then executed until disk I/O was required, which resulted in that request being messaged via shared memory to the other 'process', the 'thread' retired, the next 'thread' got picked up.

When the IO 'process' got a result, the resulting data was put in shared memory

So while the OS did not provide any measure for threads, nor processes, just concurrent Programm Counters - and to add, also not shared memory, as it was a real mode system, sharing was a thing applications just did - the system worked exactly like a threaded application would do today.

Now, a rather similar application of the early 80s, now under a virtual mode OS, solved the same problem by having the startup process creating a huge shared memory pool (well, in fact several separate for code and data), loading the code into the code memory and preparing whatever global data was to be prepared. Any process starting afterwards would detect the existing memory pools and simply hook up to the system, providing another thread to the abblication.

Again, the OS did never state anywhere that it supports threads, but it did quite well. In fact, it as well didn't say anything that code could be shared, but it did. These issues were simply assumed as given and standard.

<RANT>

This might be a fundamental difference between back then and nowadays. Back then everything was allowed and up to the developers, unlike today, were everything is VERBOTEN until explicit allowed - and developers beeing taken gently by hand by the OS to be lead thru the unknowns of simply using the system.

</RANT>

Bottom Line: Asking for first support is rather fruitless, as it's asking for wording, not functionality


*1 - Back then people did see applications running on an OS, not under an OS, as the OS wasn't the all over controlling thing of today, but rather a toolbox of services ... good old days before we added mistrust as base ingridient of OS design.

1
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – Chenmunka
    Feb 13 at 18:07
3

I think Muddle allowed multithreaded evaluation within a single address space. It had to do with evaluating A and B where A takes ten minutes to evaluate to true and B takes a millisecond to evaluate to false. Wouldn't it be smart to evaluate them quasi concurrently? This was built at MIT in the 1971-1972 timeframe.

https://en.m.wikipedia.org/wiki/MDL_(programming_language)

2

Although not the earliest example, in 1973, IBM released APL.SV (A Programming Language | Shared Variables). Variables (of any number of dimensions) could be shared between instances (effectively threads) of APL programs running on different terminals. Not mentioned in the wiki article linked to below is that APL.SV ran in supervisor mode, so there was no hardware based memory mapping or protection, just software checks like out of bounds indexing.

It was also possible for APL.SV programs to use file based synchronization and data transfer to work with batch programs that were running at the same time.

There were various ways to "glitch" array variables so that one array included the control structure for another array, allowing modification of array size to be set to double physical memory size. Each instance of an APL thread started with a standard IBM save area, where word 13 (corresponding to register 13) contained the absolute address of the save area. This allowed simple math to convert translate an absolute address into a save area relative address, allowing APL.SV programs to read|write absolute addresses in memory.

https://en.wikipedia.org/wiki/APL_(programming_language)#Commercial_availability

1

As you go back.in time, the nature of the OS itself changes.

In earlier computing eras, the OS was often (not always but often) closer to a set of callable commands - a library and framework, not an overarching *visor.

That meant that it was much more often down to the application to use what was useful, and add for itself whatever else it wanted that the OS didn't provide as standard. Even on a system level.

A trivial example of this was in early desktop computers, such as the Apple II, where even the disk controller was often manually programmed (as a primitive form of file copy prevention - create a custom on-disk format instead: the system would boot the first sector and you'd write code to be loaded from within that, to handle your own on-disk low-level format).

Contrast with today, where the OS almost exclusively provides system level capabilities, and dictates how they should/must be used, and if its not in the OS or an API provided for extending the OS you're out of luck.

So its not so much that threads were introduced by some specific OS at some specific time, or even that they were first supported by some specific OS at some specific time.

Its more like, early OS design allowed for such things, and the software model of the era was that such things were part of an application writers job. If your software needed or benefited from a capability that wasn't in the OS, you wrote it yourself, and that was the norm. The OS * supported * many, many things that weren't in the "official" manual or specs, in that way.

2
  • 1
    This doesn't seem to answer the question, inasmuch as threads libraries have from time to time been implemented in user-mode without any OS support. You could do it today if you wanted -- but most of us don't want to, because the OS has provided it. Feb 12 at 12:53
  • 2
    Actually it does answer the question, because it points out that the whole idea that shared memory is a "feature" is view biased by the perspective of a later operating system which by default configured an MMU (or at least protection unit, if not remapper) to segregate memory. In contrast the default state is that it's all open to everyone. And the question isn't about threads but about the threads with memory sharing. Feb 12 at 17:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.