Add Book to My BookshelfPurchase This Book Online

Chapter 2 - Designing Threaded Programs

Pthreads Programming
Bradford Nichols, Dick Buttlar and Jacqueline Proulx Farrell
 Copyright © 1996 O'Reilly & Associates, Inc.

Buffering Data Between Threads
The boss/worker, peer, and pipeline are models for complete multithreaded programs. Within any of these models threads transfer data to each other using buffers. In the boss/worker model, the boss must transfer requests to the workers. In the pipeline model, each thread must pass input to the thread that performs the next stage of processing. Even in the peer model, peers may often exchange data.
A thread assumes either of two roles as it exchanges data in a buffer with another thread. The thread that passes the data to another is known as the producer; the one that receives that data is known as the consumer: Figure 2-4 depicts this relationship.
Figure 2-4: Producer-consumer
The ideal producer/consumer relationship requires:
A buffer
The buffer can be any data structure accessible to both the producer and the consumer. This is a simple matter for a multithreaded program, for a such a shared buffer need only be in the process's global data region. The buffer can be just big enough to hold one data item or it can be larger, depending upon the application.
A lock
Because the buffer is shared, the producer and consumer must synchronize their access to it. With Pthreads, you would use a mutex variable as alock.
A suspend/resume mechanism
The consumer may suspend itself when the buffer contains no data for it to consume. If so, the producer must be able to resume it when it places a new item in the buffer. With Pthreads, you would arrange this mechanism using a condition variable.
State information
Some flag or variable should indicate how much data is in the buffer.
In the pseudocode in Example 2-5, the producer thread takes a lock on the shared buffer, places a work item in it, releases the lock, and resumes the consumer thread. The consumer thread is more complex. It first takes a lock on the shared buffer. If it finds the buffer empty, it releases the lock (thus giving the producer a chance to populate it with work) and hibernates. When the consumer thread awakens, it reacquires the lock, and removes a work item from the buffer.
Example 2-5: Producer/Consumer Threads (Pseudocode)
producer()
{
  .
  .
  .
lock shared buffer
place results in buffer
unlock buffer
wake up any consumer threads
  .
  .
  .
}
consumer()
{
  .
  .
  .
lock shared buffer
while state is not full {
      release lock and sleep
      awake and reacquire lock
      }
remove contents
unlock buffer
  .
  .
  .
}
If the threads share a buffer that can hold more than one data item, the producer can keep producing new items even if the consumer thread has not yet processed the previous one. In this case the producer and consumer must agree upon a mechanism for keeping track of how many items are currently in the buffer.
You can devise other permutations of the producer/consumer relationship based on the number of producer and consumer threads that access the same buffer. For example, an application that adopts the boss/worker model and uses a thread pool must accommodate a single producer (the boss) and many consumers (the workers).
A more specialized producer/consumer relationship, often used in pipelines for signal processing applications, uses a technique known as double buffering. Using double buffering, threads act as both producer and consumer to each other. In the example of double buffering shown in Figure 2-5,one set of buffers contains unprocessed data and another set contains processed data. One thread—the I/O thread—obtains unprocessed data from an I/O device and places it in a shared buffer.(In other words, it's the producer of unprocessed data.) The I/O thread also obtains processed data from another shared buffer and writes it to an I/O device. (That is, it's the consumer of processed data.) A second thread—the calculating thread—obtains unprocessed data from the shared buffer filled by the I/O thread, processes it, and places its results in another shared buffer. The calculating thread is thus the consumer of unprocessed data and the producer of processed data.
Figure 2-5: Double buffering

Previous SectionNext Section
Books24x7.com, Inc © 2000 –  Feedback