In a multi-threaded apartment, all the threads in the process that have been initialized as free-threading reside in a single apartment. Therefore, there is no need to marshal between threads. The threads need not retrieve and dispatch messages because COM does not use window messages in this model.
Calls to methods of objects in the multi-threaded apartment can be run on any thread in the apartment. There is no serialization of calls – many calls may occur to the same method or to the same object simultaneously.Objects created in the multi-threaded apartment must be able to handle calls on their methods from other threads at any time.
Multi-threaded object concurrency offers the highest performance and takes the best advantage of multi-processor hardware for cross-thread, cross-process, and cross-machine calling, since calls to objects are not serialized in any way. This means, however, that the code for objects must provide synchronization in their interface implementations, typically through the use of Win32 synchronization primitives, such as event objects, critical sections, semaphores, or mutexes. In addition, because the object doesn't control the lifetime of the threads that are accessing it, no thread-specific state may be stored in the object (in Thread-Local-Storage).
COM provides call synchronization for single-threaded apartments only. Multi-threaded apartments (containing free-threaded threads) do not receive calls while making calls (on the same thread). Multi-threaded apartments cannot make input synchronized calls. Asynchronous calls are converted to synchronous calls in multi-threaded apartments. The message filter is not called for any thread in a multi-threaded apartment.
To initialize a thread as free-threaded, call CoInitializeEx, specifying COINIT_MULTITHREADED. For information on in-process server threading, see In-process Server Threading Issues.
Multiple clients can call an object that supports free-threading simultaneously from different threads: In free threaded out-of-process servers, COM, through the RPC sub-system, creates a pool of threads in the server process and a client call (or multiple client calls) can be delivered by any of these threads at any time. An out-of-process server must also implement synchronization in its class factory. Free threaded, in-process objects can receive direct calls from multiple threads of the client.
The client can do COM work in multiple threads. All threads belong to the same multi-threaded apartment. Interface pointers are passed directly from thread to thread within a multi-threaded apartment so interface pointers are not marshaled between its threads. Message filters (implementations of IMessageFilter) are not used in multi-threaded apartments. The client thread will suspend when it makes a COM call to out-of-apartment objects and will resume when the call returns. Calls between processes are still handled by RPC.
Threads initialized with the free-threading model must implement their own synchronization. Win32 offers several means to do this: events, critical sections, mutexes, and semaphores. Following are brief descriptions of each — more information is available in the Win32 SDK.
A Win32 event object provides a way of signaling one or more threads that an event has occurred, essentially acting as a traffic cop. Any thread within a process can create an event object. A handle to the event is returned by the event-creating function CreateEvent. Once an event object has been created, threads with a handle to the object can wait on it before continuing execution.
A critical section is a section of code that requires exclusive access to some set of shared data before it can be executed. It may be used only by the threads within a single process. A critical section is like a turnstyle through which only one thread at a time may pass.
To ensure that no more than one thread at a time accesses shared data, a process's primary thread allocates a global CRITICAL_SECTION data structure and initializes its members. A thread entering a critical section calls the Win32 function EnterCriticalSection() and modifies the data structure's members.
A thread attempting to enter a critical section calls EnterCriticalSection which checks to see whether the CRITICAL_SECTION data structure has been modified. If so, another thread is currently in the critical section, so the subsequent thread is put to sleep. A thread leaving a critical section calls LeaveCriticalSection(), which resets the data structure. When a thread leaves a critical section, Windows NT wakes up one of the sleeping threads, which thereupon enters the critical section.
A mutex (short for mutual exclusion) object is a kernel object that performs the same function as a critical section, except that the mutex is accessible to threads running in different processes. Owning a mutex object is like having the floor in a debate. A process creates a mutex object by calling the Win32 function CreateMutex(), which returns a handle. The first thread requesting a mutex object obtains ownership of it. When the thread has finished with the mutex, ownership passes to other threads on a first-come, first-served basis.
A semaphore is a kernel object used to maintain a reference count on some available resource. A thread creates a semaphore for a resource by calling the Win32 function CreateSemaphore() and passing a pointer to the resource, an initial resource count, and the maximum resource count. This function returns a handle.
A thread requesting a resource passes its semaphore handle in a call to WaitForSingleObject(). The semaphore object polls the resource to determine if it is available. If so, the semaphore decrements the resource count and wakes the waiting thread. If the count is zero, the thread remains asleep until another thread releases a resource, causing the semaphore to increment the count to one.