15
$\begingroup$

Well this is general question. And if anyone want to make it implementation specific then I will prefer Unix related stuff. But first need to know following problems in generality:

I read single process can have multiple threads. Multiple threads of same process does share things among them. I want to know what they share and what not. Considering process is comprised of address space, stack, heap, global variables, code, data, OS resources, what among them is shared by threads? I have following guessings:

  1. Global variables - I have read thread shares global variable. Also while programming in Java and C#, I have made threads to share class level variables. So I am believing that the threads share global variables (though not sure whether the concepts in high level programming languages translates as is to low operating system level facts).

  2. Heap - Since global variable is stored in the heap, heap is shared among threads.

  3. Stack - Since each thread can have its own execution sequence/code, it must have its own stack on which it might push/pop its program counter contents (when say function calls and returns happen). So threads of same process do not share stack.

Now I am unsure about the sharing of following things

  1. Address space - Not sure what exactly counts under address space. But I guess address space is generally used in the context of processes, not threads. And since all threads of same process reside in the same address space as the parent process, it is said that threads share address space. (But then they maintain different stack inside same address space?)

  2. OS resources - I guess this can be very implementation specific. For example, parent process can selective give handle of same file to some of its threads and not to all. Or I am mistaking and OS resources means something other than files?

  3. Code - Threads can have different code, so sharing code is not always the case.

  4. Data - Unsure about what to consider under data. But sure that global variables are shared among threads. And sure that local variables are not similarly shared.

Overall I am considerably confused due to vague terms, super-generalizations done in the Operating Systems books and extra-implementation specific details provided online. So I am trying to find some answer that can satisfy me.

$\endgroup$
11
$\begingroup$

In general each thread has its own registers (including its own program counter), its own stack pointer, and its own stack. Everything else is shared between the threads sharing a process.

In particular a process is generally considered to consist of a set of threads sharing an address space, heap, static data, and code segments, and file descriptors*.

An address space is simply the mapping of logical addresses to specific pieces of physical memory. So when we say that all the threads in a process share the same address space we mean that when accessing a variable foo in global scope all the threads will see the same variable. Similarly, the threads may all be running a different point in the code at any particular time, but they are all permitted to call the global function bar(), which will correspond to the same function for every thread in the process.

Most modern operating systems have added a notion of thread local storage, which are variables of global scope that are not shared. The usual example of the use of this is for the variable errno. That's a single variable of global scope, but in most modern operating systems each thread is given its own local copy, so that an error in a library call on one thread won't impact the behavior of other threads.

* There is some additional process state shared by all the threads in a process, things like the process id, the signal handling, and file locks. For a complete list of process state shared by threads you need to look at the documentation for the specific threading implementation. For example, the pthreads man page.

$\endgroup$
4
$\begingroup$

Threads come up in two perspectives: operating systems, and programming languages. In both case, there is some variation in what attributes a thread has.

A minimal definition of a thread is that it's stuff that happens in sequence, one thing after another.

In a typical machine execution model, each thread has its own set of general-purpose registers and its own program counter. If the machine sets out a specific register as a stack pointer, there's one copy per thread.

From an operating system perspective, the minimum an operating system needs to do to support threads is provide a way to switch between them. This can happen either automatically (premptive multitasking or only when the thread makes an explicit request (cooperative multitasking; in that case threads are sometimes called fibers). There are also hybrid models with both preemption and cooperative yields, e.g. preemption between threads of different groups or tasks but explicit yields between threads of the same group/task. Switching between threads involves at a minimum saving the register values of the old thread and restoring the register values of the new thread.

In a multitasking operating system that provides isolation between tasks (or processes, you can treat these terms as synonyms in an OS context), each task has its own resources, in particular address space, but also open files, privileges, etc. Isolation has to be provided by the operating system kernel, an entity that's above processes. Each task normally has at least one thread — a task that doesn't execute code isn't of much use. The operating system may or may not support multiple threads in the same task; for example the original Unix didn't. A task can still run multiple threads by arranging to switch between them — this doesn't require any special privileges. This is called “user threads”, especially in a Unix context. Nowadays most Unix systems do provide kernel threads, in particular because it's the only way to have multiple threads of the same process running on different processors.

Most operating system resources apart from computation time are attached to tasks, not threads. Some operating systems (for example, Linux) explicitly delimit stacks, in which case each thread has its own; but there are OSes where the kernel doesn't know anything about stacks, they're just part of the heap as far as it's concerned. The kernel also typically manages a kernel context for each thread, which is a data structure containing information about what the thread is currently doing; this lets the kernel handle multiple threads blocked in a system call at the same time.

As far as the operating system is concerned, the threads of a task run the same code, but are at different positions in that code (different program counter values). It may or may not happen that certain parts of the code of a program are always executed in a specific threads, but there's usually common code (e.g. utility functions) that can be called from any thread. All the threads see the same data, otherwise they'd be considered different tasks; if some data can only be accessed by a particular thread, that's usually solely the purview of the programming language, not of the operating system.

In most programming languages, storage is shared between threads of the same program. This is a shared memory model of concurrent programming; it's very popular, but also very error-prone, because the programmer needs to be careful when the same data can be accessed by multiple threads as race conditions can occur. Note that even local variables can be shared between threads: “local variable” (usually) means a variable whose name is only valid during one execution of a function, but another thread can obtain a pointer to that variable and access it.

There are also programming languages where each thread has its own storage, and communication between them happens by sending messages over communication channels. This is the message passing model of concurrent programming. Erlang is the main programming language that focuses on message passing; its execution environment has a very lightweight handling of threads, and it encourages programs written with many short-lived threads, in contrast with most other programming languages where creating a thread is a relatively expensive operation and the runtime environment can't support a very large number of threads at the same time. Erlang's sequential subset (the part of the language that happens within a thread, in particular data manipulation) is (mostly) purely functional; thus a thread can send a message to another thread containing some data and neither thread needs to worry about the data being modified by the other thread while it's using it.

Some languages blend the two models by offering thread-local storage, with or without a type system to distinguish thread-local storage location from global ones. Thread-local storage is usually a convenience feature that allows a variable name to designate different storage locations in different threads.

Some (difficult) follow-ups that may be of interest to understand what threads are:

  • What is the minimum that a kernel needs to do to support multiple threads?
  • In a multiprocessor environment, what does it take to migrate a thread from one processor to another?
  • What would it take to implement cooperative multithreading (coroutines) in your favorite programming language with no support from the operating system and without using its built-in support if any? (Beware that most programming languages lack the necessary primitives to implement coroutines inside a single thread.)
  • What could a programming language look like if it had concurrency but no (explicit) concept of threads? (Prime example: the pi-calculus.)
$\endgroup$
  • $\begingroup$ This is the most interesting thing I've read in months. $\endgroup$ – JSON Nov 26 '18 at 9:06
2
$\begingroup$

That depends. If you consider threads as defined e.g by POSIX (and offered by Unix systems) or by Windows (not familiar with the later, you'd have to ask specifically), then that gives your answer (essentially as @WanderingLogic answer explains). Linux has its very own idea of threads, using the non-standard clone(2) system call. It offers rather fine-grained control of what parent and child share. It goes as far as having fork(2) and vfork(2) essentially wrappers around the internal clone(2), calling it with specific flags, i.e., you can create "threads" that share next to nothing with the parent. Look up its manual page for details, they are available on-line e.g. here. Yes, Linux does offer POSIX style threads, but much more besides.

$\endgroup$
0
$\begingroup$

Threads share:

  • Address space
  • Heap
  • Static data
  • Code segments
  • File descriptors
  • Global variables
  • Child processes
  • Pending alarms
  • Signals and signal handlers
  • Accounting information

Threads have their own:

  • Program counter
  • Registers
  • Stack
  • State
$\endgroup$

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.