A process is a collection of virtual memory space, code, data, and system resources, while a thread is code that is to be serially executed within a process. A processor executes threads, not processes, so each 32-bit application has at least one process and one thread. Prior to the introduction of multiple threads of execution, applications were all designed to run on a single thread of execution. Processes communicate with one another through messages, using RPC to pass information between processes. There is no difference to the caller in a call coming from a process on a remote machine, and a call from another process on the same machine.
While COM defines three mulitiple-threading models, some information applies to threads and processes in general. A process always has at least one thread of execution, known as the primary thread, and can have multiple threads in addition to this. Once a thread begins to execute, it continues until it is killed or until it is interrupted by a thread with higher priority (by a user action or the kernel's thread scheduler). Each thread can run separate sections of code, or multiple threads can execute the same section of code. Threads executing the same block of code maintain separate stacks. Each thread in a process shares that process's global variables and resources.
The Windows NT Scheduler determines when and how often to execute a thread according to a combination of the process's priority class attribute and the thread's base priority. You set a process's priority class attribute by calling the Win32 function SetPriorityClass(), and you set a thread's base priority with a call to SetThreadPriority().
Multi-threaded applications must avoid two threading problems: deadlocks and races. A deadlock occurs when each thread is waiting for the other to do something. A race condition occurs when one thread finishes before another on which it depends, causing the former to use a bogus value because the latter has not yet supplied a valid one.
The COM call control helps prevent deadlocks in calls between objects. COM supplies some functions specifically designed to help avoid race conditions in out-of-process servers; for information refer to Out-of-process Server Implementation Helpers.
While COM supports the single-thread-per-process model prevalent before the introduction of multiple threads of execution, writing code to take advantage of multiple threads make it possible to create more efficient applications than ever before by allowing a thread that is waiting for some time-consuming operation to allow another thread to be executed.
It is worth noting that using multiple threads is not a guarantee of better performance. In fact, because "thread-factoring" is a difficult problem, using multiple threads often causes performance problems . The key is to use multiple threads only if you are very sure of what you are doing.
In general, the simplest way to view COM's threading architecture is to think of all the COM objects in the process as divided into groups called apartments. A COM object lives in exactly one apartment, in the sense that its methods can legally be called directly only by a thread that belongs to that apartment. Any other thread that wants to call the object must go through a proxy.
There are two types of apartments: single-threaded apartments, and multi-threaded apartments.
Single-threaded Apartments — each thread that uses OLE is in a separate "apartment", and COM synchronizes all incoming calls with the windows message queue. A process with a single thread of execution is simply a special case of this model.
Multi-threaded Apartments — Multiple threads in a single free-threaded apartment use COM and calls to COM objects are synchronized by the objects themselves.
A description of communication between single-threaded apartments and multi-threaded apartments within the same process is in Single-/Multi-threaded Communication.
Single-threaded apartments consist of exactly one thread, so all COM objects that live in a single-threaded apartment can receive method calls only from the one thread that belongs to that apartment. All method calls to a COM object in a single-threaded apartment are synchronized with the windows message queue for the single-threaded apartment's thread.
Multi-threaded apartments consist of one or more threads, so all COM objects that live in an multi-threaded apartment can receive method calls directly from any of the threads that belong to the multi-threaded apartment. Threads in a multi-theaded apartment use a model called "free-threading". OLE does not provide any synchronization of method calls to COM objects in a multi-threaded apartment. In particular, this means that the COM object must provide it's own synchronization if needed.
A process can have zero or more single-threaded apartments, and zero or one multi-threaded apartment. One way of looking at this is the following:
In reality, however, all process are apartment-model, it is just that some apartments have a single thread and some apartments have multiple threads. The threading model really applies to an apartment, not to a process. It can also apply to a class of objects, but it doesn't really apply to a component, such as a DLL, but to the object classes within the DLL. Different classes in a DLL can have different threading models.
In a process, the main apartment is the first to be initialized. In a single-threaded process, this remains the only apartment. Call parameters are marshaled between apartments, and COM handles the synchronization through messaging. If you designate multiple threads in a process to be free-threaded, all free threads reside in a single apartment, parameters are passed directly to any thread in the apartment, and you must handle all synchronization. In a process with both free-threading and apartment threading, all free threads reside in a single apartment, and all other apartments are single-threaded apartments. A process that does COM work is a collection of apartments with, at most, one multi-threaded apartment but any number of single-threaded apartments.
The threading models in COM provide the mechanism for clients and servers that use different threading architectures to work together. Calls among objects with different threading models in different processes are naturally supported. From the perspective of the calling object, all calls to objects outside a process behave identically, no matter how the object being called is threaded. Likewise, from the perspective of the object being called, arriving calls behave identically, regardless of the threading model of the caller.
Interaction between a client and an out-of-process object is straightforward even when they use different threading models because the client and object are in different processes and COM is involved in remoting calls from the client to the object. COM, interposed between the client and the server, can provide the code for the threading models to interoperate, with standard marshaling and RPC. For example, if a single-threaded object is called simultaneously by multiple free-threaded clients, the calls will be synchronized by COM by placing corresponding window messages in the server's message queue. The object's apartment will receive one call each time it retrieves and dispatches messages.
Some care must be taken to ensure that in-process servers interact properly with their clients. These issues are described in In-process Server Threading Issues.
The most important issue in programming with a multithreaded model is to ensure that the code is thread-safe, so messages intended for a particular thread go only to that thread, and access to threads is protected.