Threads What is a Process?

= process ready queue preemption CPU terminated new arrival waiting for disk waiting for terminal waiting for timer

3.1.3 Threads

Multithreaded processes contain multiple threads of execution A process may be multithreaded, in which case many executions of the code co-exist together. Each such thread has its own CPU state and stack, but they share the rest of the address space and the environment. In terms of abstractions, a thread embodies the abstraction of the flow of the com- putation, or in other words, what the CPU does. A multithreaded process is therefore like a computer with multiple CPUs, that may operate in parallel. All of these CPUs have access to the computer’s memory contents and its peripherals e.g. disk and net- work. CPU CPU CPU CPU ... memory disk The main exception in this picture is the stacks. A stack is actually a record of the flow of the computation: it contains a frame for each function call, including saved register values, return address, and local storage for this function. Therefore each thread must have its own stack. Exercise 36 In a multithreaded program, is it safe for the compiler to use registers to temporarily store global variables? And how about using registers to store local variables defined within a function? Exercise 37 Can one thread access local variables of another? Is doing so a good idea? 48 Threads are useful for programming Multithreading is sometimes useful as a tool for structuring the program. For exam- ple, a server process may create a separate thread to handle each request it receives. Thus each thread does not have to worry about additional requests that arrive while it is working — such requests will be handled by other threads. Another use of multithreading is the implementation of asynchronous IO opera- tions, thereby overlapping IO with computation. The idea is that one thread performs the IO operations, while another computes. Only the IO thread blocks to wait for the IO operation to complete. In the meanwhile, the other thread can continue to run. Exercise 38 Asynchronous IO is obviously useful for writing data, which can be done in the background. but can it also be used for reading? The drawback of using threads is that they may be hard to control. In particular, threads programming is susceptible to race conditions, where the results depend on the order in which threads perform certain operations on shared data. As operating systems also have this problem, we will discuss it below in Chapter 4. Threads are an operating system abstraction Threads are often implemented at the operating system level, by having multiple thread entities associated with each process these are sometimes called kernel threads, or light-weight processes LWP. To do so, the PCB is split, with the parts that de- scribe the computation moving to the thread descriptors. Each thread then has its own stack and descriptor, which includes space to store register contents when the thread is not running. However they share all the rest of the environment, including the address space and open files. Schematically, the kernel data structures and memory layout needed to implement kernel threads may look something like this: PCB user kernel text data heap stack 1 stack 2 stack 3 stack 4 thread descriptors threads state storage CPU registers priority stack accounting state memory files user text data heap Exercise 39 If one thread allocates a data structure from the heap, can other threads access it? 49 At the beginning of this chapter, we said that a process is a program in execution. But when multiple operating-system-level threads exist within a process, it is actually the threads that are the active entities that represent program execution. Thus it is threads that change from one state running, ready, blocked to another. In particular, it is threads that block waiting for an event, and threads that are scheduled to run by the operating system scheduler. Alternatively, threads can be implemented at user level An alternative implementation is user-level threads. In this approach, the operating system does not know about the existence of threads. As far as the operating system is concerned, the process has a single thread of execution. But the program being run by this thread is actually a thread package, which provides support for multiple threads. This by necessity replicates many services provided by the operating system, e.g. the scheduling of threads and the bookkeeping involved in handling them. But it reduces the overhead considerably because everything is done at user level without a trap into the operating system. Schematically, the kernel data structures and memory layout needed to implement user threads may look something like this: stack 3 stack 2 stack 1 stack 4 kernel user PCB stack text data state state stack thread descriptors heap accounting priority CPU registers storage priority CPU registers storage accounting memory files user Note the replication of data structures and work. At the operating system level, data about the process as a whole is maintained in the PCB and used for scheduling. But when it runs, the thread package creates independent threads, each with its own stack, and maintains data about them to perform its own internal scheduling. Exercise 40 Are there any drawbacks for using user-level threads? The problem with user-level threads is that the operating system does not know about them. At the operating system level, a single process represents all the threads. Thus if one thread performs an IO operation, the whole process is blocked waiting for the IO to complete, implying that all threads are blocked. 50 A possible solution to such problems is to use several operating system threads as the basis for a user-level thread package. Thus when a user-level thread performs an IO, only the operating system thread currently running it is blocked, and the other operating system threads can still be used to execute the other user-level threads. Exploiting multiprocessors requires operating system threads A special case where threads are useful is when running on a multiprocessor a com- puter with several physical processors. In this case, the different threads may exe- cute simultaneously on different processors. This leads to a possible speedup of the computation due to the use of parallelism. Naturally, such parallelism will only arise if operating system threads are used. User-level threads that are multiplexed on a single operating system process cannot use more than one processor at a time. The following table summarizes the properties of kernel threads and user threads, and contrasts them with processes: processes kernel threads user threads protected from each other, require operating system to communicate share address space, simple communication, useful for application structuring high overhead: all oper- ations require a kernel trap, significant work medium overhead: oper- ations require a kernel trap, but little work low overhead: everything is done at user level independent: if one blocks, this does not affect the others if a thread blocks the whole process is blocked can run on different processors in a multiprocessor all share the same pro- cessor system specific API, programs are not portable the same thread library may be available on sev- eral systems one size fits all application-specific thread management is possible In the following, our discussion of processes is generally applicable to threads as well. In particular, the scheduling of threads can use the same policies described below for processes.

3.1.4 Operations on Processes and Threads