[ Previous | Next | Contents | Glossary | Home | Search ]
Motif 2.1 Programmer's Guide



Writing Threaded Applications

In general, writing multithreaded Motif applications requires familiarity with:

  1. General concurrent programming principles.

  2. Specific threads interfaces.

  3. New interfaces in X11R6 Xt for multithreaded applications.

  4. Why Multithreading?

    In general, there are two ways of specifying program concurrency: through two or more processes or through two or more threads. The former approach is known as multiprocessing and the latter approach is known as multithreading. There are two main reasons for choosing multithreading over multiprocessing: multiprocessing requires expensive interprocess communication, while separate threads can directly share data. Compared to process creation and process context switches, thread creation and thread context switches are inexpensive operations.

    Input Processing Loop

    Motif applications are based on a paradigm whereby the application operates in an infinite loop, checking for input and dispatching the input to the appropriate place. The input consists of X events arriving from the display server, input from alternate input sources, and timeout values. The code for the input processing loop looks something like this:

    while(TRUE)    {
            XEvent event;
            XtAppNextEvent(app, &event);
            XtDispatchEvent(&event);
    }

    The app refers to an application context in the Motif application. XtAppNextEvent removes and returns the event from the head of app's X event queue. If the X event queue is empty, XtAppNextEvent waits for an X event to arrive, meanwhile looking at alternate input sources and timeout values and calling any callback procedures triggered by them. After XtAppNextEvent returns, XtDispatchEvent dispatches the X event to the appropriate place. Dispatching the X event typically involves invoking some event handlers, action procedures, or callback procedures. For the purposes of our discussion, we will henceforth refer to event handlers, action procedures, callback procedures, input callback procedures and timer callback procedures, collectively as "callbacks."

    Typically, every Motif application does its useful work in its "callbacks." If the work done in a callback" is time consuming (and indivisible), the processing of the next event on the X event queue may be noticeably delayed, causing degradation in the responsiveness of the application. Multithreading can help avoid the delay in the processing of X events and thus alleviate the problem of poor responsiveness (or interactivity).

    However, this is not the only motivation. Others include a more "natural" program structure for inherently concurrent Motif applications and better performance of Motif applications on multiple-processor machines (or on a configuration of multiple machines).

    Xt Interfaces For Multithreading

    Motif is so closely coupled with the X Toolkit. The X11R6 interfaces for multithreading are required only if the multithreaded application calls Motif/Xt interfaces in multiple threads. It is possible to write multithreaded Motif applications in which Motif/Xt interfaces are invoked only from a single thread. In such applications, the interfaces may not be required. In addition to these interfaces, X11R6 declares functions and data types used by multithreaded programs, in the file X11/Xthreads.h. Since threads interfaces are operating system dependent, including this file instead of the system specific file increases portability.

    Initializing Xt For Use In Multiple Threads

    A Motif application that creates multiple application threads must call XtToolkitThreadInitialize, which initializes Xt for use in multiple threads. XtToolkitThreadInitialize returns True if the given Xt supports multithreading, otherwise it returns False.

    XtToolkitThreadInitialize may be called before or after XtToolkitInitialize and it may be called more than once. However, it must not be called concurrently from multiple threads. An application must call XtToolkitThreadInitialize before calling XtAppInitialize, XtSetLanguageProc, XtOpenApplication or XtCreateApplicationContext.

    Using XtAppLock and XtAppUnlock

    Concurrency can be programmed into a Motif application by using a model where each AppContext has one thread. This ensures that each event loop within each AppContext can operate independently in its own thread. This is important in order to keep the data and event processing in each AppContext from becoming corrupted. Enabling only one thread in each AppContext requires a locking strategy per AppContext.

    To lock and unlock an AppContext and all widgets and display connections in the AppContext, an application must use XtAppLock and XtAppUnlock.

    All Motif and Xt functions that take an AppContext, widget or display connection as a parameter, implicitly lock their associated AppContext for the duration of the function call. Therefore, with a few exceptions, an application does not need to call XtAppLock or XtAppUnlock. The first exception is the situation in which an application needs to make a series of Xt function calls atomically. In such a situation, an application can enclose the series of Xt calls within a matching pair of XtAppLock and XtAppUnlock. For example, if an application wants to check and update the height of a widget atomically, it might do it as follows:

    XtAppContext app;
    Dimension ht = 0;
    Widget w;
    XtAppLock(app);
    XtVaGetValues(w, XtNheight, &ht, NULL);
    if((int)ht < 10) {
            ht += 10;
            XtVaSetValues(w, XtNheight, ht, NULL);
    }
    XtAppUnlock(app);

    Other exceptions are the Motif resources that are returned as live pointers to the actual resource storage within the widget instance. Some examples include:

    1. XmNchildren: returns a pointer to the corresponding Composite field XmNitems

    2. XmNselectedItems: returns pointers to the corresponding List fields

    3. XmNsource: returns a pointer to the corresponding Text field

      In these cases, the application again needs to envelop the XtGetValues call within AppLocks so that another thread operating on the same instance doesn't change the resource value. The application might also want to make a copy of the retrieved resource.

      To provide mutually exclusive access to global data structures application writers can use XtProcessLock and XtProcessUnlock.

      Both XtAppLock and XtProcessLock may be called recursively. To unlock to the top-level, XtAppUnlock and XtProcessUnlock must be called the same number of times as XtAppLock and XtProcessLock, respectively. To lock an AppContext and Xt's global data at the same time, first call XtAppLock and then XtProcessLock. To unlock, first call XtProcessUnlock and then XtAppLock. The order is important to avoid deadlock.

    4. New XtAppMainLoop

      As we have seen, the R5 XtAppMainLoop is an infinite loop that calls XtAppNextEvent and then XtDispatchEvent. In a multithreaded-safe Xt, an infinite XtAppMainLoop would prevent an input processing thread from exiting its XtAppMainLoop, without simultaneously exiting its thread. This situation may lead to the following problems:

      1. Memory leaks.

      2. Dangling threads.

      3. Complicated synchronization between the input processing thread and other threads.

        In order to elegantly address these problems, the X11R6 version of Xt reimplemented XtAppMainLoop. It is still a conditional loop that calls XtAppNextEvent and then XtDispatchEvent, but it now checks at the bottom of the input loop to determine if the application is finished with the AppContext by checking a new exit flag. If the exit flag is set to True, XtAppMainLoop exits the input processing loop. The following example shows one implementation of the new XtAppMainLoop.

        XtAppContext app;
        do {
                XEvent event;
                XtAppNextEvent(app, &event);
                XtDispatchEvent(&event);
        } while(XtAppGetExitFlag(app) == FALSE);

        The AppContext's exit flag can be set to True by calling XtAppSetExitFlag. The value of the exit flag can be obtained through XtAppGetExitFlag. The new XtAppMainLoop offers an effective way of destroying an AppContext, without having to exit from the input processing thread.

      4. Destroying An Application Context

        In multithreaded Motif applications, the recommended way of destroying an AppContext is: After the input processing thread returns from XtAppMainLoop, and after it has been determined that no other thread is referring to the associated AppContext, call XtDestroyApplicationContext. Use XtAppGetExitFlag to synchronize between an input processing thread and other threads. Call XtAppSetExitFlag in the exit callback. This causes the associated XtAppMainLoop to exit but does not terminate the input thread.

        Event Management In Multiple Threads

        When multiple threads call into event management functions concurrently, they return from these functions in last-in first-out order. Another way of looking at this is to imagine that if multiple threads get blocked for input in any of the event management functions, they are pushed on a per-application-context stack of blocked threads. Whenever there is some input to process, a thread is popped off the stack and allowed to process the input.

        For example, let's assume thread A, the "input processing thread", is blocked for input in XtAppMainLoop so that it is on the per-application-context stack of blocked threads. Let us further assume that subsequently one of the "other" threads, thread B, detects some error condition and decides to popup a message box and temporarily take over input processing from thread A. The fact that blocked threads get stacked allows thread B to easily take over input processing from thread A. The way thread B accomplishes this is to execute a form of input processing loop within a matched pair of XtAppLock and XtAppUnlock, as follows:

        /* This code is in a thread other than the input processing thread
        */
        XtAppContext app;
        Widget error_dialog;
        XEvent event;
        XtAppLock(app); /* Lock the AppContext */
        XtPopup(error_dialog, XtGrabExclusive); /* popup error dialog */
        do {/* Take over input processing */
                XtAppNextEvent(app, &event);
                if(/* some boolean condition involving event */)
                        XtDispatchEvent(&event);
        } while(/* some boolean condition */)
        XtAppUnlock(app);

        Because thread B calls into XtAppNextEvent after thread A does, thread B gets stacked above thread A. Subsequently, whenever input arrives, thread B is "popped" off the stack and allowed to process the input.


        [ Previous | Next | Contents | Glossary | Home | Search ]