-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproducer_consumer.cpp
More file actions
120 lines (102 loc) · 3.52 KB
/
Copy pathproducer_consumer.cpp
File metadata and controls
120 lines (102 loc) · 3.52 KB
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/**
* @file producer_consumer.cpp
* @brief Demonstrates the classic Producer-Consumer synchronization problem.
*
* THE PROBLEM:
* Two types of threads share a bounded buffer (queue):
* - Producer: Creates items and pushes them into the buffer.
* - Consumer: Pops items from the buffer and processes them.
*
* Rules:
* - Producer must WAIT if the buffer is full.
* - Consumer must WAIT if the buffer is empty.
* - Only one thread can access the buffer at a time.
*
* THE SOLUTION:
* - std::mutex protects the shared buffer (mutual exclusion).
* - std::condition_variable lets the consumer sleep until there IS data,
* instead of busy-waiting (spinning in a loop checking if buffer is empty).
*
* KEY CONCEPTS:
* - cv.wait(lock, predicate):
* 1. Atomically releases the mutex and puts the thread to sleep.
* 2. Wakes up when notify_one() is called.
* 3. Re-checks the predicate. If true, proceeds. If false, sleeps again.
* (This prevents "spurious wakeups" — a real OS phenomenon!)
*
* - cv.notify_one(): Wakes up ONE waiting thread.
*
* BUILD & RUN:
* cmake -B build -S . && cmake --build build && ./build/test_sync_producer
*/
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <queue>
#include <thread>
// Shared bounded buffer — both producer and consumer access this.
std::queue<int> buffer;
// Mutex to protect the buffer from simultaneous access.
std::mutex mtx;
// Condition variable — lets the consumer sleep until data is available.
std::condition_variable cv;
/**
* @brief Produces 5 items and pushes them into the shared buffer.
*
* After pushing each item, it notifies the consumer that data is available.
* Sleeps 1 second between items to simulate real work.
*/
void producer()
{
for (int i = 1; i <= 5; i++)
{
{
// Lock the buffer before modifying it.
std::lock_guard<std::mutex> lock(mtx);
buffer.push(i);
std::cout << "Produced: " << i << std::endl;
}
// ^^^ lock_guard is destroyed here, so the mutex is released.
// Wake up one waiting consumer: "Hey, there's data now!"
cv.notify_one();
// Simulate work (producing the next item takes time).
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
/**
* @brief Consumes 5 items from the shared buffer.
*
* Uses a condition variable to sleep until the buffer has items,
* instead of busy-waiting (which wastes CPU).
*/
void consumer()
{
for (int i = 1; i <= 5; i++)
{
// unique_lock is required by cv.wait() (lock_guard won't work here
// because cv.wait needs to temporarily release and re-acquire the lock).
std::unique_lock<std::mutex> lock(mtx);
// Wait until the buffer is not empty.
// If buffer is empty → release lock, sleep.
// When notified → re-acquire lock, check predicate again.
cv.wait(lock, [] {
return !buffer.empty();
});
// At this point, we hold the lock AND the buffer has data.
int item = buffer.front();
buffer.pop();
std::cout << "Consumed: " << item << std::endl;
// lock is automatically released here when unique_lock goes out of scope.
}
}
int main()
{
std::thread producerThread(producer);
std::thread consumerThread(consumer);
// Wait for both threads to finish.
producerThread.join();
consumerThread.join();
std::cout << "\nAll work completed!" << std::endl;
return 0;
}