Fluent::Programmer

    Home Blog About
  • Home
  • Blog
  • About

Beautiful code #8 - Conditional variable in multi threading (C++) πŸ‘‹

  • Fluent Programmer
  •   4 min read

Quick summary ↬  Here is an example of a C++ threading code that demonstrates how to use multiple threads and synchronize them using condition variable. This is basic producer-consumer scenario of how Apache Kafka streams data.

Beautiful code #8

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>

std::queue<int> buffer;
const int buffer_size = 5;
std::mutex mtx;
std::condition_variable buffer_not_full;
std::condition_variable buffer_not_empty;

void producer()
{
    for (int i = 1; i <= 10; ++i) {
        std::this_thread::sleep_for(std::chrono::seconds(1)); // Simulate some work to produce an item
        std::unique_lock<std::mutex> lock(mtx);
        buffer_not_full.wait(lock, []{ return buffer.size() < buffer_size; });
        buffer.push(i);
        std::cout << "Produced item: " << i << std::endl;
        buffer_not_empty.notify_all();
    }
}

void consumer()
{
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        buffer_not_empty.wait(lock, []{ return !buffer.empty(); });
        int item = buffer.front();
        buffer.pop();
        std::cout << "Consumed item: " << item << std::endl;
        buffer_not_full.notify_all();
    }
}

int main()
{
    std::thread producer_thread(producer);
    std::thread consumer_thread(consumer);
    
    producer_thread.join();
    consumer_thread.join();
    
    return 0;
}

Beautiful code #8 explanation

This code demonstrates a producer-consumer scenario using a fixed-size buffer and condition variables to synchronize the producer and consumer threads. Let’s go through the code and understand how it works:

  1. We include the necessary header files for working with threads, queues, mutexes, and condition variables.

  2. We declare a shared buffer as a queue to hold the produced items. We also define the buffer_size constant to specify the maximum size of the buffer.

  3. We define a mutex (mtx) to protect access to the buffer and two condition variables: buffer_not_full to signal that the buffer is not full and the producer can produce items, and buffer_not_empty to signal that the buffer is not empty and the consumer can consume items.

  4. The producer function represents the producer thread. It produces items by pushing integers from 1 to 10 into the buffer. Before modifying the buffer, it acquires a unique lock on the mutex using std::unique_lock<std::mutex> lock(mtx).

  5. To avoid producing items when the buffer is full, the producer thread waits for the buffer_not_full condition variable to notify it that the buffer has space available. It uses the lambda function [] { return buffer.size() < buffer_size; } as the predicate to check if the buffer is not full.

  6. Once the producer thread is awakened and the buffer has space available, it pushes the item into the buffer, prints the produced item, and then notifies the buffer_not_empty condition variable to indicate that there are items in the buffer for the consumer thread to consume.

  7. The consumer function represents the consumer thread. It repeatedly waits for the buffer_not_empty condition variable to notify it that the buffer has items available for consumption.

  8. When the buffer_not_empty condition variable notifies the consumer thread, it acquires a unique lock on the mutex and checks if the buffer is not empty by using the lambda function [] { return !buffer.empty(); } as the predicate.

  9. If the buffer is not empty, the consumer thread retrieves the front item from the buffer, pops it, and prints the consumed item.

  10. Finally, the consumer thread notifies the buffer_not_full condition variable to indicate that there is space available in the buffer for the producer thread to produce more items.

  11. In the main function, we create the producer and consumer threads and wait for them to finish using the join function.

Overall, this code demonstrates how condition variables can be used in a producer-consumer scenario to synchronize the producer and consumer threads and ensure that the buffer doesn’t become full or empty.

You will also like β€” More Articles

https://fluentprogrammer.com/beautiful-code-1-using-define-templates-r-value-reference/ https://fluentprogrammer.com/beautiful-code-2-enable_if_t-template-inside-a-template/ https://fluentprogrammer.com/beautiful-code-3-no_unique_address_cpp_20-feature/ http://fluentprogrammer.com/beautiful-code-4-any_of-none_of-all_of/ https://fluentprogrammer.com/beautiful-code-5-for_each-optional/

You may like this

About The Author

Fluentprogrammer doesn't need coffee to program. They run on pure caffeine and lines of code.

Email Newsletter

Table of Contents

  • Beautiful code #8
  • Beautiful code #8 explanation
  • You will also like β€” More Articles
  • You may like this
  • C++
  • Beautiful code series

Unhealthy love with dark corners of C++

Founded by an engineer to help engineers. 2021–2023.

  • About us
  • Privacy policy