|
Threads and Apartments Author: Brad Wilson Table of Contents IntroductionWhat is a threading model? What is an apartment? The single threaded apartment The multithreaded apartment Matching threading models and apartments A word about pumping messages and reentrancy So what's "both" threading? How do I set the threading model in ATL? Recommended reading About the author
This article is intended for the new COM developer. It gives detailed information about apartments and their relationship to threading and synchronization. The goal is to demystify what is a very important, yet under-documented system in COM. COM has a very powerful model of thinking (which is extended with MTS and again with COM+). The model is this: components have a “context” in which they run, which is configured to meet their needs. When you specify a threading model, you are telling COM what type of runtime environment your object expects, with respect to multithreaded access. COM provides four separate threading models, each with a distinct threading requirement: Single Threaded The single threaded model is the original model, and the only one supported in 16-bit Windows (obviously, since Windows 3.x doesn’t support threads). In the single threaded model, COM guarantees that all accesses to all instances of your objects will occur on a single thread. Apartment Threaded Apartment threading is available on all 32-bit Windows platforms except Win32s (again, because Windows 3.x doesn’t support threading). In the apartment threaded mode, COM guarantees that all access to a single instance of your object will occur on a single thread, but multiple instances may be created into multiple different threads. Free Threaded Free threading is available on all 32-bit Windows platforms with DCOM. Windows NT 4.0, Windows 98, and Windows 2000 all support free threading natively; Windows 95 requires the DCOM service pack to support free threading. In the free threaded mode, COM allows multiple threads to access a single instance of your object. Both Threaded The both threaded model is a special model that’s best understood once you have a good grasp of all the other threading models, and rules involved therein. It will be addressed at the end of this article. In order to enforce the threading rules, the COM team invented the idea of an apartment. This is the “environment” that an individual instance of an object runs in. In COM, there are two types of apartments: the single threaded apartment (abbreviated “STA”), and the multithreaded apartment (abbreviated “MTA”). In addition, there is a special kind of STA called the “Main STA”. The Main STA is the first STA created in a given process. Each thread that wants to use COM must initialize itself with COM. At the time that it initializes itself, it declares what type of apartment should be associated with that thread. If you call CoInitialize() (or its OLE counterpart, OleInitialize()), then you are telling the system to create a single threaded apartment for you and associate it the thread. You can call CoInitialize() on any system that supports COM. If you call CoInitializeEx(), then you can explicitly specify the type of apartment as one of the flags in the second parameter. If you specify COINIT_MULTITHREADED, the COM runtime will associate the thread with the MTA; if you specify COINIT_APARTMENTTHREADED, the COM runtime will associate the thread with an STA. You can only call CoInitializeEx on systems that have DCOM installed. The single threaded apartment, as its name implies, only allows a single thread to run in it. For every thread that calls CoInitializeEx with COINIT_APARTMENTTHREADED (or CoInitialize or OleInitialize), the COM runtime creates a new single threaded apartment for the thread. Therefore, a process can have multiple single threaded apartments. The first single threaded apartment created in a process is also known as the Main STA. We’ll talk about the significance of the Main STA in a moment. When an object is created into an STA, it is guaranteed that no other threads will access it but the thread that created it. That means an object created into an STA can safely use Thread Local Storage (TLS) to store information that is specific to the instance of the object. An object which has a threading model of “single” will always be created into the Main STA, even if the thread that created it is already in an STA. That’s because the “single” threading model guarantees that all instances of the objects will be accessed from a single thread. COM makes this guarantee by always creating “single” threaded objects into the Main STA. “Single” threaded objects do not need to synchronize any data, because of this guarantee. An object that has a thread model of “apartment” will always be created into an STA. However, each individual object may be created into a different STA, so any static, global or shared data between instances of objects must be synchronized, since these things may be accessed from multiple threads. The multithreaded apartment, unlike the STA, permits multiple threads to be in it at one time. There is at most one multithreaded apartment in a process. The first thread that enters the multithreaded apartment, or the first multithreaded object that’s created, causes the multithreaded apartment to be created. Subsequence threads that enter the MTA and multithread objects will all enter the same, single MTA. When an object is created into the MTA, there are no reentrancy guarantees. An object that lives in the multithreaded apartment may be called by any number of threads simultaneously, and is responsible for synchronizing not only the global, static, and shared data, but also the instance data in the object. Something that’s also not very obvious to the beginning COM user is that objects in the MTA may be called at any time from threads they’ve never seen before. First, there is the fact that one thread in the MTA could create the object, then pass its pointer off to another thread in the MTA. Additionally, the RPC runtime creates threads when out of process calls arrive, and this RPC threads can directly call into MTA objects. An object in the MTA should not use TLS to store any state specific data, and should shield its data from concurrent access by different threads through some kind of serialization constructs. Matching threading models and apartments When a thread initializes COM, it declares the type of apartment it wishes to live in. When creating objects, if it creates components that can happily live in the apartment it’s in, it will get direct pointers to the objects. So, what happens when the object’s threading model doesn’t conform to the apartment the current thread is in? The system creates the object in the type of context it expects to be in, and then passes back a pointer to a “proxy”. The “proxy” concept is too large to address in this article. It is an important issue, so you should educate yourself on them. For the purposes of this discussion, just assume that a proxy lives in your apartment, and knows how to route your calls to the real object that’s living in another apartment. Object creation from an STA If the thread is in an STA, and creates an “apartment” threaded object, it is created in the threads apartment, and a direct pointer is passed back. If the thread is in an STA, and creates a “single” threaded object, the COM runtime creates the object into the main STA. If this thread’s apartment is the Main STA, then it returns a direct pointer; otherwise, it returns a proxy. If the thread is in an STA, and creates a “free” threaded object, then the COM runtime will look to see if the MTA has been created for this process yet. If not, it will create one. Then it creates a new object into the MTA, and passes back a proxy. Object creation from the MTA If the thread is in an MTA, and creates a “free” threaded object, the COM runtime creates the object into the MTA, and returns a direct pointer. If the thread is in an MTA, and creates a “single” or “apartment” threaded object, then the COM runtime checks to see if the Main STA has been created yet. If not, it creates the Main STA. It then creates the object into the Main STA, and returns a proxy. A word about pumping messages and reentrancy When a thread is in the MTA, the system truly blocks when a call goes through a proxy. Because multiple threads can enter the MTA, this blocking isn’t a problem, because the RPC runtime can always create a new thread to call into the object if there is a callback. However, when a thread is in the STA, it cannot block, because the method call it makes via the proxy may result in a callback. If the thread is blocked, it will never be able to service the callback, and the two objects go into deadlock. So how does the COM runtime deal with this? The answer is: messages. When calling out of an STA via a proxy, the thread “pseudo-blocks”; that is, it continues to pump messages for the thread. When the RPC service gets a call into the thread for a COM object, it services that call via the message loop. This prevents the deadlock situation. It’s important for threads in an STA to pump messages if they need to receive COM messages. Otherwise, a deadlock situation can occur. Also notice that this means that even a “single” threaded object can be reentered because of a callback. While this typically isn’t a major problem for most objects, you need to ensure that a callback isn’t going to corrupt the state of your object. The “both” threading model indicates that a component is willing to run in either an STA or an MTA. Since any object that can run in an MTA is fully synchronized, it seems logical that any object that can be “free” threaded, can also be “both” threaded. So, why would you ever use “free” threading? Isn’t the win of not using a proxy reason enough to always use “both” threading? The answer is not always. COM components, of course, can also be COM clients. One object may create any number of objects to help it perform its work. An object may also create threads that perform work for it, and call back into the object. If the object were marked as “both” threaded, and then created on a thread that was in an STA, it would be created in that STA. Then, any free threaded components it needed to access would be created in the MTA, and a proxy would be returned. All calls between the objects would be marshaled. The issue of worker threads is less obvious for the new COM developer. What’s so bad about creating a worker thread that calls back into an object, no matter what apartment it’s in? As long as the object can handle calls from multiple threads, it should be okay, right? No! Imagine a situation where a “both” threaded object is created into an STA, and creates worker threads that call back into it. The worker threads take a direct pointer to the object, and when their work is done, it calls back into the object. The object is synchronized, so it can handle this “cheat”. What happens, though, if the result of the method call from the thread into the object results in a method call into another component that the object has created? Well, if you’re lucky, you have a proxy, and the RPC layer will tell you you tried to call from the wrong thread, and rejects the call. If you’re not so lucky, you’ll have a direct pointer, call into the object, and possibly crash it horribly, because it wasn’t designed to run multithreaded (you’re in the STA, after all!) So, choosing between “free” and “both” threading models depends on the type of work that the component is going to be doing. If the object does its work on its own without much assistance from other components, then it is an excellent candidate for the “both” threading model. If, however, it relies on free threaded components or worker threads, it should restrict itself to the “free” threading model for performance reasons. How do I set the threading model in ATL? When you do raw COM programming, all you need to do is ensure that your object performs the right kind of threading support, and register the threading model in the registry. When you use ATL 3.0 to build your COM components, there are a few steps you should take to ensure that your components are set for the correct threading model. 1. Change the #define in stdafx.h Open stdafx.h in your project, and change the #define that sets your default threading model. By default, the line reads:
#define _ATL_APARTMENT_THREADED
You want to change it to _ATL_SINGLE_THREADED or _ATL_FREE_THREADED as appropriate. If you have multiple components in your project with different threading models, default the one that requires the most synchronization; eg, if you have some free threaded components, use _ATL_FREE_THREADED; if you have some apartment threaded components, but no free threaded, then use _ATL_SINGLE_THREADED. 2. Change the threading model class you derive from Open the header file for the C++ class that implements your component. In the inheritance tree, your first class that you inherit is typically:
public
CComObjectRootEx<CComSingleThreadModel>
The template parameter that you provide to CComObjectRootEx is the type of threading model that your component uses. It should be CComSingleThreadModel for “single” or “apartment” threaded components, and CComMultiThreadModel for “free” or “both” threaded components. 3. Change the .RGS file Open the .RGS file for your component. In it you will see a line that resembles:
val ThreadingModel = s ‘Apartment’
Change the value to ‘Single’, ‘Apartment’, ‘Free’ or ‘Both’ as appropriate. 4. Make sure you understand the implications! The most important thing to changing threading models is to understand the implications it has on the run time environment of your component. This is by far the easiest thing to forget. I’ve seen more than my fair share of components that ran fine in apartment mode, that suddenly get buggy in free threaded mode, because access to a state variable wasn’t synchronized. There are a lot of books out there about COM, especially if you use C++ and ATL to write your COM components. For the beginner, I recommend “Inside COM” by Dale Rogerson. After you’ve got a little experience under your belt, you will find that “Essential COM” by Don Box and “Effective COM” by Don Box, Keith Brown, Tim Ewald, and Chris Sells are invaluable reading. “Essential COM” is especially instructive about the “why” of COM; most books cover the “how” only, and leave the “why” as an exercise to the reader. C++ programmers should consider reading “Effective C++” by Scott Meyers to strengthen their skills before tackling serious pure-C++ COM projects. ATL programmers should consider reading “Professional ATL COM Programming” by Dr. Richard Grimes, and “ATL Internals” by Brent Rector and Chris Sells. Don't forget, other titles as well as the ones mentioned above are available at the COM Bookstore What do you think of this article? You can also write a review. We will publish the best ones here on this article. Send your review to [email protected]. Mail a question to the author!! As part of the IDevResource commitment to Open Publishing, all of our authors are available to answer all of your trickiest questions at Author Central. For information about the authors, or to mail a question, visit them at Author Central. Want to read more articles by this author? Try these:
More COM Library articles: CAtlBitmapButton - ATL/WTL Ownerdraw Superclassed Bitmap Button By Amit Dey. ATL COM and ADO By Paddy Srinivas. ATL COM and ADO By Amit Dey. COM Singletons: A Dangerous Animal By Richard Blewett. ASP COM Objects By Jan Verhoeven. SafeArrays - For the Beginner By A. Abdul Azeez. COM+ Basics - Creating your first COM+ Application By Martin Lapierre. COM+ - the backbone of Windows DNA By Mahesh Bhide. Exploring COM Threading and Apartments By Anthony Toivonen. COM on Linux By Frank Rem. ATL Server By Richard Grimes. ATL Internals - Part 2 By Shivesh Viswanathan. How to use DDX with WTL By Girish Bharadwaj. COM Patterns By Tony Toivonen. COM+ Object Pooling By Jeremiah Talkar. What are COM Pipes? By Richard Grimes. Java COM Integration - Use Visual J++ to implement COM Objects By Gopalan Suresh Raj. Developing an MSMQ Server Application using VJ++ By Gopalan Suresh Raj. Developing an MSMQ Client using VJ++ By Gopalan Suresh Raj. What is Async COM? By Richard Grimes. Coding a DCOM Server Component from IDL By Gopalan Suresh Raj. Coding a DCOM Client By Gopalan Suresh Raj. Threads and Apartments By Brad Wilson. WTL Architecture By Richard Grimes. The MTS Series by Gopalan Suresh Raj: Microsoft Transaction Server By Gopalan Suresh Raj. Developing a Simple MTS Server Component By Gopalan Suresh Raj. Developing a Simple MTS Client Application By Gopalan Suresh Raj. Developing The Bank Account IDL By Gopalan Suresh Raj. MTS Server Component By Gopalan Suresh Raj. MTS Client By Gopalan Suresh Raj. Other Articles Active Template Library: Architecture & Internals By Shivesh Viswanathan. Microsoft Transaction Server By Richard Grimes. What COM is all about By Richard Grimes. String Binding Moniker By Frank Rem. Author: Brad Wilson Brad Wilson is a senior software engineer are MarketScape, Inc. in Colorado Springs, Colorado, where he architects and implements remotePORTAL. He has ten years of experience in software development, including extensive work in C++ and COM. He enjoys smoking a pipe and talking philosophy with anyone who has the constitution to listen to him. He lives in Denver with his fiancйe Lisa, their two cats Stewie and Ed, and an extensive collection of books, movies, and music. Go to Brad's pages in Author Central. Contribute to IDR: To contribute an article to IDR, a click here.
|
|