July 22, 2011, 2:39 a.m.
posted by mark
Processes versus Threads
Section 5.1 described the benefits of concurrent servers, which can be implemented using multiple processes or multiple threads. The primary tradeoffs in this dimension involve robustness, efficiency, and scalability.
Multiprocessing. A process is the OS entity that provides the context for executing program instructions. Each process manages certain resources, such as virtual memory, I/O handles, and signal handlers, and is protected from other OS processes via memory management unit (MMU) hardware. Processes created by fork() on UNIX and by CreateProcess() on Win32 execute concurrently in different address spaces than their callers. These mechanisms are examined closely in Chapter 8.
Earlier-generation operating systems, such as BSD UNIX [MBKQ96], provided processes with just one thread of control. This single-threaded process model can enhance robustness since processes can't interfere with one another without explicit programmer intervention. For example, processes can collaborate with each other only via shared memory or local IPC mechanisms, as shown in Figure (1).
It's hard to use single-threaded processes, however, to develop certain types of applications, particularly high-performance or real-time servers. Servers that need to communicate with each other or respond to management requests must use some form of IPC, which adds to their complexity. It's also hard to exert efficient, fine-grain control over scheduling and process priority using multiprocessing.
Multithreading. To alleviate the problems with processes outlined above, most OS platforms now support multiple threads within a process. A thread is a single sequence of instruction steps executed in the context of a process's protection domain, as shown in Figure (2). In addition to an instruction pointer, a thread manages certain resources, such as a runtime stack of function activation records, a set of registers, signal masks, priorities, and thread-specific data. If multiple CPUs are available, services in multithreaded servers can execute in parallel [EKB+92]. On many versions of UNIX, threads are spawned by pthread_create() ; on Win32 they are spawned by CreateThread().
Implementing concurrent networked applications that perform multiple operations in separate threads rather than in separate processes can reduce the following sources of concurrency overhead:
Thread creation and context switching. Since threads maintain less state than processes, thread creation and context switching overhead can be lower than the corresponding process lifecycle activities. For example, process-wide resources, such as virtual address mappings and caches, needn't change when switching between threads in a process.
Synchronization. It may not be necessary to switch between kernel mode and user mode when scheduling and executing an application thread. Likewise, intraprocess synchronization is often less expensive than interprocess synchronization because the objects being synchronized are local to a process and may therefore require no kernel intervention. In contrast, interprocess thread synchronization generally involves the OS kernel.
Data copying. Threads can share information using process-local memory, which has the following benefits:
It's often more efficient than using shared memory or local IPC mechanisms to communicate between processes because data needn't be copied through the kernel.
It's easier to use C++ objects in process-local memory since there's no problem with class virtual table layouts (see Sidebar 3 on page 30).
For example, cooperating database services that reference common data structures resident in process-local memory are simpler and more efficient to implement with multiple threads than with multiple processes.
As a result of these optimizations, multithreading can often improve application performance significantly. For example, I/O-bound applications can benefit from multithreading since compute-intensive services can be overlapped with disk and network operations. Just because an OS platform supports threads, however, doesn't imply that all applications should be multithreaded. In particular, the following limitations arise when using multithreading to implement concurrent applications:
Performance degradation. A common misconception is that threading inherently improves application performance. Many times, however, threading doesn't improve performance for several reasons, including:
Compute-bound applications on a uniprocessor won't benefit from multithreading since computations and communication can't run in parallel.
Fine-grained locking strategies can yield high synchronization overhead, which prevents applications from fully exploiting the benefits of parallel processing [SS95].
Reduced robustness. To reduce context switching and synchronization overhead, threads receive little or no MMU protection from each other. Executing all tasks via threads within a single process address space can reduce application robustness for several reasons, including:
Separate threads within the same process address space aren't well protected from each other. One faulty service in a process can therefore corrupt global data shared by services running on other threads in the process. This, in turn, may produce incorrect results, crash an entire process, or cause an application to hang indefinitely.
Certain OS functions invoked in one thread can have undesirable side effects on an entire process; for example, the UNIX exit() and Win32 ExitProcess() functions have the side effect of terminating all the threads within a process.
Lack of fine-grained access control. On most operating systems, a process is the granularity of access control. Another limitation with multithreading, therefore, is that all threads within a process share the same user ID and access privileges to files and other protected resources. To prevent accidental or intentional access to unauthorized resources, network services, such as TELNET, that base their security mechanisms on process ownership often run in separate processes.
Logging service The concurrent implementations of the logging server in our networked logging service can be implemented in a variety of ways. Chapters 8 and 9 use multiple processes and multiple threads, respectively, to implement concurrent logging servers.