Herb Sutter is a bestselling author and consultant on software development topics, and a software architect at Microsoft. He can be contacted at www.gotw.ca.
In a recent article, we covered reasons why threads should strive to make their data private and communicate using asynchronous messages.[1] Each thread that needs to receive input should have an inbound message queue and organize its work around an event-driven message pump mainline that services the queue requests, idealized as follows:
// An idealized thread mainline
//
do {
message = queue.pop() // get the message
// (might wait)
message->run(); // and execute it
} while( !done ); // check for exit
But what happens when this thread must remain responsive to new incoming messages that have to be handled quickly, even when we're in the middle of servicing an earlier lower-priority message that may take a long time to process?
If all the messages must be handled on this same thread, then we have a problem. Fortunately, we also have two good solutions, both of which follow the same basic strategy: somehow break apart the large piece of work to allow the thread to perform other work in between, interleaved between the chunks of the large item. Let's consider the two major ways to implement that interleaving, and their respective tradeoffs in the areas of fairness and performance.
Example: Breaking Up a Potentially Long-Running Operation
Consider this potentially expensive message we might be asked to execute:
// A simplified message type to accomplish some
// long operation
//
class LongOperation : public Message {
public:
void run() {
LongHelper helper = GetHelper();
// issue: what if this loop could take a long time?
for( int i = 0; i < items.size(); ++i ) {
helper->render( items[i] );
}
helper->print();
}
}
This thread may be a background worker that runs all the work we want to get off the GUI thread (see [1]). Alternatively, in cases where it is impossible to obey the good hygiene of getting all significant work off the GUI thread (for example, because for some reason the work may need to happen on the GUI thread itself for legacy or OS-specific reasons), this thread may be the GUI itself. Whatever the case, what matters is that to remain responsive to other messages we need to break up LongOperation.run into smaller pieces and interleave them with the processing of other messages.


