FREE Subscription to Dr. Dobb’s Digest: Same Great Content, New Digital Edition
Site Archive (Complete)
C++
Email
Print
Reprint

add to:
Del.icio.us
Digg
Google
Furl
Slashdot
Y! MyWeb
Blink
October 10, 2007

Apply Critical Sections Consistently

(Page 2 of 4)

Example 1: One Producer, Many Consumers, Using Locks

Imagine we have a single Producer thread that produces a number of tasks for Consumer threads to perform. The Producer pushes the tasks into a shared queue, and finally pushes a special done sentinel to mark the end of the work. The queue object is protected at all times using mutex mut, which the Producer holds only as long as necessary for a single push, so that Consumers can start executing their tasks concurrently with the Producer. For further efficiency, to avoid making the Consumers busy-wait whenever the queue is initially or temporarily empty, the Producer will also signal condition variable cv every time something has been added to the queue:








// One Producer thread // while( ThereAreMoreTasks() ) { task = AllocateAndBuildNewTask(); mut.lock(); // enter critical section queue.push( task ); // add new task mut.unlock(); // exit critical section cv.notify(); } mut.lock(); // enter critical section queue.push( done ); // add sentinel value; that's all folks mut.unlock(); // exit critical section cv.notify();

On the consumption side, the Consumer threads each pull individual tasks off the completed queue. Here, myTask is an ordinary variable local to each Consumer:

// K Consumer threads
//
myTask = null;
while( myTask != done ) {
  mut.lock();  	// enter critical section
  while( queue.empty() )	// if nothing's there, to avoid busy-waiting we'll
    cv.wait( mut );  	// release and reenter critical section
  myTask = queue.first();	// take a copy of the task
  if( myTask != done ) 	// remove it from queue unless it was the sentinel, 
    queue.pop();	// which we want to leave there for others to see
  mut.unlock();	// exit critical section
  if( myTask != done )
    DoWork( myTask ); 
}


All of these critical sections are easy to see. This code simply protects the data structure using the same mutex for its entire shared lifetime, and it's easy to check that you did it right—just look to make sure the code only refers to queue when inside some critical section that holds a lock on mut.

The primary expression of critical sections is the explicit locking done on the mutex. The condition variable is just icing on the cake—an optimization that lets us express an efficient wait point in the middle of a locked section so that the Consumer race cars don't have to expend needless energy spinning their tires (in this case, their lock/unlock loop) while waiting for the Producer to give them the green light.

Previous Page | 1 Apply Critical Sections Consistently | 2 Example 1: One Producer, Many Consumers, Using Locks | 3 Example 2: One Producer, Many Consumers, Using Lock-Free Mailboxes | 4 Example 3: Don't Try to Turn Critical Sections Inside Out Next Page
TOP 5 ARTICLES
No Top Articles.



MICROSITES
FEATURED TOPIC

ADDITIONAL TOPICS

INFO-LINK