Effect of Hyperthreading on a Thread Object for Windows Animation: Part 2

Paul Milenkovic

Copyright 2003

Return to Part 1Skip to Part 3

An in-process thread is yet another technique for pacing animation screen updates, one that is not reliant on having control over the message loop. A MessageBox() modal alert or a menu selection preempts the application message loop with an out-of-application message loop, and an in-process thread offers a way of keeping an animation active when those actions occur.

The Windows graphical user interface (GUI) and supporting API is single-threaded according to an event-driven cooperative multitasking model. Events in the form of Windows messages are stored in a queue and each message is dispatched to a single task among a group of cooperating tasks. Each active window handle object that receives messages through a WinProc() procedure represents a separate task in that system. The time slice for a task is the time required for a window handle object to receive a single message and to process that message. Since the dispatching of messages is not preemptive, a task has to “cooperate” by completing its task in a timely manner so that the next message can be dispatched. The advantage of cooperative multitasking over preemptive is that while tasks have to be “well behaved” about completing their time slice, the switches between tasks occur at well-defined places in the code, simplifying the synchronization of tasks and the management of resources shared between tasks.

A task can yield control of its time slice with either the SendMessage() or PostMessage() API calls. SendMessage() is a blocking yield; the current task is suspended, the designated message is dispatched, and the current task is resumed where it left off after the designated message has been processed. PostMessage() is a non-blocking yield; the current task continues to completion, and the designated message is dispatched some time after completion of the message processed by the current task. SendMessage() functions like a subroutine call while PostMessage() places as message on the event queue for later processing. SendMessage() is processed with less overhead than PostMessage(). SendMessage() is most often used for inter-task, inter-window communication unless the delayed dispatch of PostMessage() is required.

The user interface and screen paints are run by a single GUI thread, and animation pacing will be controlled by a separate update thread. The simplest design is to have the update thread signal the GUI thread to conduct an animation update and to have the GUI thread signal the update thread when it has completed an update, so the update thread can signal the next animation update. An event object created by function CreateEvent() allows for such inter-thread signaling. A call to WaitForSingleObject() blocks execution of a thread until the designed event object is signaled by another thread, and a blocked thread yields to all other threads. The function MsgWaitForMultipleObjects() allows combining the wait for an event object signal with a wait for a Windows message if waiting on an event object is to be integrated with a message loop. SetEvent() signals a thread blocked on WaitForSingleObject() or MsgWaitForMultipleObjects() to proceed. Using a pair of event objects, a pair of threads can signal each other to engage in alternating periods of execution, and the technique uses the thread scheduler to in effect perform cooperative multitasking, yielding control between threads at known points in the code. The scheduler, however, can still preempt a thread that stops cooperating.

Like event loop idle time processing, using MsgWaitForMultipleObjects() on the GUI thread side has a problem with out-of-application event loops. The message loops inside the MessageBox() and the menu and task bar handlers do not incorporate MsgWaitForMultipleObjects(), so animation stops whenever an alert box appears or a menu is selected. Fortunately, SendMessage() and PostMessage() can be safely invoked from the update thread to signal the GUI thread with a Windows message, and even the MessageBox() and menu and task bar message loops know how to handle Windows messages. When SendMessage() and PostMessage() are invoked on a window handle owned by another thread, these functions performed the required inter-thread signaling and synchronization. Java also has a single-threaded GUI, and the GUI object methods InvokeNow() and InvokeLater() are the recommended means for a helper thread to interact with the GUI thread. InvokeNow() behaves much like SendMessage() and InvokeLater() is the counterpart to PostMessage().

The animation update works with the GUI thread along with a separate update thread. It employs one Windows message in the WM_USER range for the update thread to signal the GUI thread with PostMessage(), and it employs one event object for the GUI thread to signal the update thread when the GUI thread is done processing the posted message. The update thread incorporates the code

while running do begin

// Blocked until signaled by GUI thread

WaitForSingleObject(upd_nextH,1000);

if running then begin

// Yield to other pending threads

Sleep(0);

// Signal GUI thread

PostMessage(upd_wH,WM_USER+1000,0,0);

end;

end;

EndThread(0);



while the GUI thread responds to the WM_USER+1000 message sent to the window handle upd_wH with



UpdateNext; // Perform animation update

SetEvent(upd_nextH); // Signal update thread to post next update



The call to Sleep(0) is specified by MSDN to yield all of current thread's time slice to any other thread of equal or greater priority. The update thread is created and launched with a low priority to allow it to yield to the GUI thread or other threads so that this cycle of posting a WM_USER message and signaling when the WM_USER message has been processed allows other messages in the GUI thread and other threads to proceed.

This technique for a self-paced animation update works much like a multimedia timer. A multimedia timer is really a helper thread that invokes a callback procedure at specified time intervals. Because it is paced by a timer, it does not need to be signaled to proceed. Remember the cryptic documentation on MSDN that you can really put anything inside the callback apart from a PostMessage()? The callback is invoked by the timer thread, and PostMessage() does the necessary signaling and dispatching of the GUI thread to respond to the timer. Why do they tell you to only use PostMessage(); why won't SendMessage() work?

The problem occurs when you try an shut down the helper thread from your GUI thread when your program terminates. The shutdown is initiated by setting running to false, signaling upd_nextH, and invoking WaitForSingleObject() on the thread handle to wait for the thread to terminate. Because SendMessage() is a blocking wait, there is a small probability that the helper thread is blocked on SendMessage() at the time shutdown is initiated. But if the GUI thread is blocked on WaitForSingleObject() waiting for the thread to shut down, it cannot process the message supplied by SendMessage() from another thread, and deadlock occurs. SendMessage() works as a straight subroutine call inside a thread, but between threads it uses the message queue and blocks until the target thread of SendMessage inputs from the message queue. The end result is that your application program can fail to terminate when you select Files Exit, only it does this perhaps 1 out of every 10 times, and this is extremely frustrating to the application developer who has not been informed this can happen. MSDN tells you to use only PostMessage() but they fail to tell you why (something as simple as “using SendMessage() in place of PostMessage() can result in timer deadlock when the timer is shut down” would have been of enormous help).

The animation update thread works effectively on all versions of Windows until it encounters Windows 2000 or Windows XP on a Pentium 4 processor with hyper-threading enabled. The procedure UpdateNext() that responds to the animation update could be called upon to update more than one window handle object generating multiple WM_PAINT messages for each WM_USER+1000 message. On a hyper-threading computer, only one WM_PAINT message will be processed per animation update, and the remaining windows will receive sporadic WM_PAINT messages and fail to animate smoothly.

Traces of execution on the GUI and update thread reveal the following pattern. On Windows versions 98 through XP running on a non hyper-threaded processor, invoking PostMessage() has the effect of blocking the update thread, processing the posted WM_USER+1000 message followed by processing any WM_PAINT messages generated by the animation update before allowing the update thread to proceed. WaitForSingleObject() and Sleep(0) do not block because upd_nextH is signaled and the GUI message queue emptied by the time PostMessage() unblocks. With hyper-threading, however, PostMessage() does not block the update thread, and the posted animation update message (WM_USER+1000) is dispatched either preemptively prior to WaitForSingleObject() or upon blocking on the signal from the GUI thread. Sleep(0) also blocks the update thread, and it dispatches one WM_PAINT message in the GUI thread but no more than one WM_PAINT message, even if more are pending.

Oddly enough, Sleep(1) is found to dispatch all pending WM_PAINT messages. Sleep(1) is supposed to block for 1 ms, subject to Windows not conducting an uninterruptible operation taking more than 1 ms. Sleep(0) is documented as yielding the remainder of a thread time slice to other threads, and while the time slice is variable on Windows 2000 and XP, it is on the order of 20 ms or longer. I cannot determine if Sleep(0) is dispatching single or multiple WM_PAINT messages on a non hyper-threading machine because PostMessage() is cleaning out the message queue before the update thread loops back up to Sleep(0).

At some point how hyper-threading changes the thread scheduling, whether this is a feature or a bug, and whether this bug (non-conformance with a published specification regarding Sleep(0)) will be corrected as a patch or as a newer version of Windows may be revealed. In the mean time, work must go on and a workaround must be developed. Attorneys have an expression “another bite at the apple” to enumerate the number of legal venues they can use to litigate a matter until they obtain a desired outcome. The workaround involves signaling the update thread to get enough bites at the apple (the Sleep()) call so that all pending WM_PAINT messages get processed. The idea is to signal the update thread to get it to invoke Sleep() to dispatch pending paint messages without having invoked the animation update that produces fresh paint messages and to keep doing this until all paint messages are processed so one can invoke the animation update prior to signaling the update thread again. The update thread and GUI thread code fragments are

// Update thread

while running do begin

WaitForSingleObject(upd_nextH,1000);

if running then begin

Sleep(nsleep);

PostMessage(upd_wH,WM_USER+1000,0,0);

end;

end;

EndThread(0);



// GUI thread response to WM_USER+1000

if PeekMessage(msg,0,WM_PAINT,WM_PAINT,PM_NOREMOVE) then

//If paint messages are backed up, signal update thread without doing

// next update so Sleep(nsleep) can dispatch paint messages from whatever

// message loop -- main program, message box, menu -- is active.

Inc(nsleep)

else begin

if nsleep > 0 then

Dec(nsleep);

UpdateNext;

end;

SetEvent(upd_nextH);



This code invokes Sleep(0) as the default mode of operation – without hyper-threading, the message queue should be empty of WM_PAINT messages at the PeekMessage() call, and the non hyper-threading update thread loop will see Sleep(0). In the hyper-threading case, PeekMessage() will find one or more WM_PAINT messages in the queue, and setting nsleep to 1 and signaling the update thread without invoking the next animation update should empty the queue. If that doesn't work, as a defensive programming measure against the undocumented behavior of Sleep(1), nsleep is incremented until enough bites of the apple have emptied the message queue of paint messages.

At this point the skeptical reader may ask, “why go through this strange procedure simply to empty the message queue of paint messages? Why not invoke PeekMessage() with PM_REMOVE to empty the queue from the GUI thread?” Why not process all messages to clear the queue prior to signaling the update thread? In that case one could dispense with the update thread entirely. The reason is that it would lock up menu selection. Not only does menu selection have its own message loop, that message loop does not rely on filtering and dispatching messages with TranslateMessage() and DispatchMessage(), so processing the messages meant for that message loop with an application message loop does not work. Perhaps WM_PAINT messages can be preempted that way, but if you are not going to process all messages, you need the update thread back, and having the update thread, you may as well use the update thread to dispatch the out-of-application message loops and process messages in the intended order. The whole reason for the update thread is to dispatch animation updates to out-of-application message loops. Otherwise, one would use the event loop idle time technique.

Return to Part 1Go to Part 3