- Producer-consumer work queues
- A common use of mutex-locks is in establishing a producer-consumer relationship between threads.
- The producer creates tasks and inserts them into a work-queue.
- The consumer threads pick up tasks from the task queue and execute them.
- Consider that the task queue can hold only one task.
- In a general case, the task queue may be longer but is typically of bounded size.
- A simple (and incorrect) threaded program would associate a producer thread with creating a task
- and placing it in a shared data structure
- and the consumer threads with picking up tasks from this shared data structure and executing them.
- $$
- However, this simple version does not account for the following possibilities:
- 1
- The producer thread must not overwrite the shared buffer when the previous task has not been picked up by a consumer thread.
- 2
- The consumer threads must not pick up tasks until there is something present in the shared data structure.
- 3
- Individual consumer threads should pick up tasks one at a time.
- $$
- To implement this, we can use a variable called task_available.
- If this variable is 0, consumer threads must wait, but the producer thread can insert tasks into the shared data structure task_queue.
- If task_available is equal to 1, the producer thread must wait to insert the task into the shared data structure but one of the consumer threads can pick up the task available.
All of these operations on the variable task_available should be protected by mutex-locks to ensure that only one thread is executing test-update on it.
- The create_task and process_task functions are left outside the critical region, making the critical section as small as possible.
- but insert_into_queue and extract_from_queue functions are left inside the critical region.
- Inside because if the lock is relinquished after updating task_available but not inserting or extracting the task,
- other threads may gain access to the shared data structure while the insertion or extraction is in progress, resulting in errors.
- For producer-consumer work queues
- The producer thread creates a task and waits for space on the queue.
- This is indicated by the variable task_available being 0.
- The test and update of this variable as well as insertion and extraction from the shared queue are protected by a mutex called task_queue_lock.
- Once space is available on the task queue, the recently created task is inserted into the task queue and the availability of the task is signaled by setting task_available to 1.
- Within the producer thread, the fact that the recently created task has been inserted into the queue is signaled by the variable inserted being set to 1, which allows the producer to produce the next task.
- Irrespective of whether a recently created task is successfully inserted into the queue or not, the lock is relinquished.
- This allows consumer threads to pick up work from the queue in case there is work on the queue to begin with.
- If the lock is not relinquished, threads would deadlock since a consumer would not be able to get the lock to pick up the task and the producer would not be able to insert its task into the task queue.
- The consumer thread waits for a task to become available and executes it when available.
- As was the case with the producer thread, the consumer relinquishes the lock in each iteration of the while loop to allow the producer to insert work into the queue if there was none.
Cem Ozdogan
2010-12-06