Writing a multithreaded application ideally requires that the libraries the application uses be MT-safe. Otherwise the application must ensure that only one thread is inside the library at any given time. This could lead to unnatural design and coding strategies and to sacrificing parallelism in the application. Making a library MT-safe basically means that the library should take the responsibility for ensuring consistent internal state even when the application starts multiple threads that may simultaneously invoke its API.
This section describes how to develop a MT-safe Motif widget library.
The Xt library in Release-6 of the X-Window system (X11R6) is MT-safe. A brief description of the MT-safety provided by Xt follows.
Concurrency refers to the number of threads that can safely execute a piece of code at the same time. Xt permits one thread per XtAppContext to be active. So multiple threads can be active within Xt simultaneously, if each one of them is spawned in distinct XtAppContexts. Thus the concurrency provided by Xt is at the XtAppContext level.
This model is elegant for application programmers. However it can be difficult for widget programmers since it allows multiple active threads within the widget methods concurrently, and hence all global data access within the widget library needs to be suitably protected.
Fortunately, most data in a widget is stored in the widget instance. And since a widget instance hierarchy is rooted in an XtAppContext, the one thread per XtAppContext model protects instance data from corruption due to multiple threads.
Xt implements the one thread per XtAppContext policy by protecting all public entry points into the library with AppLocks. Every public Xt API locks the application context at entry and releases it at exit. Xt provides the following functions to lock and unlock the XtAppContext:
Note that these locks are recursive; that is, the same thread invoking XtAppLock multiple times with the same application context will not deadlock.
Xt provides ProcessLocks to ensure that only one thread at a time can execute critical code paths. All access to global data is considered critical code and is done while the process is locked. Xt provides the following functions to lock and unlock the ProcessLock:
Note that these locks are recursive; that is, the same thread invoking XtProcessLock multiple times will not deadlock.
Xt avoids deadlock by mandating a simple locking hierarchy: always acquire the AppLock first; then acquire the ProcessLock.
Motif uses the same locking strategies implemented in Xt:
All public entry points into the widget should be enveloped with the XtAppLock, XtAppUnlock pair. Private entry points intended for internal use only need not be protected, as the invoking public API will have acquired AppContext locks. Public functions that are just wrappers around Xt functions (such as XmCreate* functions) do not require AppLocks since the Xt function already does this.
Typically, widget APIs have either a Widget, Display or Screen parameter. The AppContext can be obtained from these using the following Xt functions:
Widget APIs that do not have either a Widget, Display or Screen parameter cannot be protected using AppLocks. A potential deadlock exists if such a function wants to acquire the ProcessLock (because the function has not acquired the AppLock yet), resulting in a break in the locking hierarchy. In this situation, one must ensure that once the ProcessLock is obtained, an AppLock should not be attempted until the ProcessLock is released. Basically this means that Xt function calls, user callbacks, or other non-safe routines that can AppLock should not be invoked from within ProcessLock'ed regions in this function. For example, consider a widget public API XmFooBar:
Dimension XmFooBar(Widget w) { ..... return w->core.width; }
The MT-safe version of the above function would be:
Dimension XmFooBar(Widget w) { int return_value; XtAppContext app = XtWidgetToApplicationContext(w); XtAppLock(app); ...... return_value = w->core.width; XtAppUnlock(app); return return_value; }
Global data can be classified as either read-only or read-write:
Other random pieces of global data (mostly function static variables) can be usually dealt with by either making them instance fields or stack variables. If that does not work, ProcessLocks can be used to protect global access regions. Exceptions are those variables that are used to communicate state information across function. These can be handled using thread specific storage to retain their semantics.
A discussion on the common global categories in Motif widgets follows.
A widget basically has two parts: a class record and an instance record. The instance record is per-widget and is dynamically allocated when the widget is created, and thus is not global data. Class records however are statically allocated global data. The class record typically contains methods and few data fields. The class methods are either set up at compile time and never changed thereafter or are set up at runtime during ClassInitialize (to resolve inheritance). Class initialization happens once when the first widget of that class is created. This process happens within Xt and is already under ProcessLocks. Thus we can be assured that these class method pointers get written into only once by only one thread.
So, we can consider the above two cases to be read-only situations and hence in general, class methods can be accessed without locks.
However, the Xt Base classes (RectObj, Core, Constraint, Shell) are exceptions since Motif does change their class method pointers at runtime to implement Pre and Post hooks. Hence we consider all the Xt Classes' class methods to be read-write and we need to access those method pointers within XtProcessLocks.
Consider a code fragment that invokes the resize method:
{ WidgetClass wc; Widget w; (*wc->core_class.resize)(w); }
The MT-safe version would be
{ WidgetClass wc; Widget w; XtWidgetProc resize; XtProcessLock(); resize = wc->core_class.resize; XtProcessUnlock(); (*resize)(w); }
Most class fields are read-write and hence require XtProcessLock protection.
XContexts are an Xlib abstraction to implement per display storage. Since Display connections cannot be shared among XtAppContexts, we are ensured that the data hanging off XContexts also cannot be shared among XtAppContexts. Hence we don't need to protect these data using Locks.
However, ProcessLock protection may be needed to protect the initialization of the XContext (created using XUniqueContext if the initialization does not happen in a Widget's ClassInitialize procedure.
Widgets define their resource lists as static global XtResource structures. These lists are set up at compile time and never changed thereafter. Hence these can be considered as read-only data and do not require access protection.
Motif widgets also define synthetic resource tables as static global XmSyntheticResource structures. These lists are also read-only and do not require protection.
Widgets define their Actions and Translations using static global XtActionRec and XtTranslation structures respectively. These are set up at compile time and only modified by the (MT-safe) Xt intrinsics. Hence these can be considered read-only data and do not require access protection.
Motif defines the Trait mechanism as a means of sharing behavior among widget classes. A widget establishes a trait by setting up a static global XmTrait structure. This table is set up at compile time and its contents should not be changed thereafter. Thus Trait tables also do not require any protection.
Xt Resource converters are used for inter-type resource conversions. The converter can return the converted data either in local static storage or in the heap. The choice is made based on the address passed by the caller to store the converted value in. If the address is NULL, static storage is used, else the memory (on the heap) pointed to by address is used. Since static storage should be avoided for MT-safety, always pass in a valid heap address when invoking any converter. The guidelines when using/writing resource converters are:
XtCallProc procedures are used in widget resource lists to provide default values for a resource. Default procedures typically use static storage to store the default value. Fortunately, this usage is MT safe, since the code in Xt that invokes this procedure and copies over the default value into the resource location is within XtProcessLocks. Thus, the statics in default procedures present no problems.
Widgets may use their own event loops to implement Drag and Drop and other such esoteric features.
The event processing functions in R6 Xt that block while waiting for events drop the XtAppLock during the wait (XtAppNextEvent, XtAppPeekEvent, and XAppProcessEvent ). This is done so that other threads get a chance to issue Xt calls while the event processing thread waits for events. However in the case of Drag and Drop, we really do not want other threads to get into Xt and change state while the Drag operation is in progress. There is also the danger that another event processing thread might come in and "steal" events meant for this thread.
To avoid this situation, event loops should be replaced with "busy" loops that check the event queue without giving up the XtAppLock.
The following code implements a "busy" event loop that does not give up the XtAppLock by replacing XtAppNextEvent:
while (XtAppGetExitFlag(app) == False) { XEvent event; #ifndef MT_SAFE XtAppNextEvent(app, &event); XtDispatchEvent(&event); /* Process it */ #else /* MT_SAFE version ... */ XtInputMask mask; while (!(mask = XtAppPending(app))) /* EMPTY */; /* Busy waiting - so that we don't lose our Lock! */ if (mask & XtIMXEvent) { /* We have a XEvent */ /* Get the XEvent - we know its there! Note that XtAppNextEvent would also process timers/alternate inputs */ XtAppNextEvent(app, &event); /* No blocking, since an event is ready */ XtDispatchEvent(&event); /* Process it */ } else /* Not a XEvent, its an alternate input/timer event - Process it. XtAppProcessEvent(app, mask); /* No blocking, since an event is ready */ }
Some of the C library functions are inherently MT unsafe. A
MT-safe libc implementation will provide alternatives to those functions. This
section lists the common libc functions that are MT-unsafe along with their
safe POSIX equivalents.
MT-Unsafe
|
MT-Safe
|
---|---|
getlogin
|
getlogin_r
|
ttyname
|
ttyname_r
|
readdir
|
readdir_r
|
strtok
|
strtok_r
|
ctime, localtime...
|
ctime_r, localtime_r...
|
getgrnam, getgrgid
|
getgrnam_r, getgrgid_r
|
getpwnam, getpwuid
|
getpwnam_r, getpwuid_r
|
The following Motif library functions are MT-safe:
Occasionally, one might end up using globals to communicate state information across functions. The only way to retain the proper semantics in this case might be to use thread specific storage. The Xthreads.h header file in X11R6 provides a set of wrapper functions to support thread_specific storage.
The sequence of steps to creating and using Thread Specific Storage are:
xthread_key_create(xthread_key_t *key_return);
void xthread_getspecific(xthread_key_t key, void **value_return); int xthread_setspecific(xthread_key_t key, const void *value);
Note that xthread_getspecific returns NULL the first time its invoked for a specific thread, so that gives us the chance to set up the heap storage for each thread.
Thread specific storage can be expensive, hence it should be utilized as a last resort.
For MT-safety, widget resources should be designed so that XtGetValues returns a copy of the resource value (in the heap). The GetValuesHook method or the XmSyntheticResource export_proc can be used to achieve this.
If a live pointer to the actual instance field has to be returned, flag this situation as a MT-unsafe usage so that the application programmer is aware of it.
Following is a summary of the discussions in this section: