C++并发学习三:生产者-消费者模型(有界缓冲区)
要求:实现一个有Capacity的Buffer,并用生产者消费者同时Push/Pop
1. 失败尝试
第一次实现的时候忘记notify_one(),像下面这样
void push(T item) {
std::unique_lock<std::mutex> lock(mtx_);
not_full_cv_.wait(lock, [&]{ return queue_.size() < capacity_; });
queue_.push(item);
std::cout << "[Producer] queue size = " << queue_.size() << std::endl;
// not_empty_cv_.notify_one(); <---- 忘记写了
}
T pop() {
std::unique_lock<std::mutex> lock(mtx_);
not_empty_cv_.wait(lock, [&]{ return !queue_.empty(); });
T res = queue_.front();
queue_.pop();
// not_full_cv_.notify_one();
return res;
}
没有notify的后果:Producer在写满capacity_后就卡住了
[Producer] Pushing 0
[Producer] queue size = 1
[Consumer] Popped 0
[Producer] Pushing 1
[Producer] queue size = 1
[Producer] Pushing 2
[Producer] queue size = 2
[Producer] Pushing 3
[Producer] queue size = 3
[Consumer] Popped 1
[Producer] Pushing 4
[Producer] queue size = 3
[Producer] Pushing 5
[Producer] queue size = 4
[Producer] Pushing 6
[Producer] queue size = 5
[Producer] Pushing 7
[Consumer] Popped 2
[Consumer] Popped 3
[Consumer] Popped 4
[Consumer] Popped 5
[Consumer] Popped 6
...无限等待没有继续打印...
2. 正确答案
加上notify_one()后成功跑通,贴下完整代码:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <vector>
template <typename T>
class BoundedBuffer {
public:
BoundedBuffer(size_t size) : capacity_(size) {}
void push(T item) {
std::unique_lock<std::mutex> lock(mtx_);
not_full_cv_.wait(lock, [&]{ return queue_.size() < capacity_; });
queue_.push(item);
std::cout << "[Producer] queue size = " << queue_.size() << std::endl;
not_empty_cv_.notify_one();
}
T pop() {
std::unique_lock<std::mutex> lock(mtx_);
not_empty_cv_.wait(lock, [&]{ return !queue_.empty(); });
T res = queue_.front();
queue_.pop();
not_full_cv_.notify_one();
return res;
}
private:
size_t capacity_;
std::queue<T> queue_;
std::mutex mtx_;
// 当缓冲区不满时,通知生产者
std::condition_variable not_full_cv_;
// 当缓冲区不空时,通知消费者
std::condition_variable not_empty_cv_;
};
int main() {
BoundedBuffer<int> buffer(5);
std::thread producer([&]() {
for (int i=0; i<10; i++) {
std::cout << "[Producer] Pushing " << i << std::endl;
buffer.push(i);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
});
std::thread consumer([&]() {
for (int i=0; i<10; i++) {
int item = buffer.pop();
std::cout << "[Consumer] Popped " << item << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(400));
}
});
producer.join();
consumer.join();
return 0;
};
运行成功
[Producer] Pushing 0
[Producer] queue size = 1
[Consumer] Popped 0
[Producer] Pushing 1
[Producer] queue size = 1
[Producer] Pushing 2
[Producer] queue size = 2
[Producer] Pushing 3
[Producer] queue size = 3
[Consumer] Popped 1
[Producer] Pushing 4
[Producer] queue size = 3
[Producer] Pushing 5
[Producer] queue size = 4
[Producer] Pushing 6
[Producer] queue size = 5
[Producer] Pushing 7
[Consumer] Popped 2
[Producer] queue size = 5
[Producer] Pushing 8
[Consumer] Popped 3
[Producer] queue size = 5
[Producer] Pushing 9
[Consumer] Popped 4
[Producer] queue size = 5
[Consumer] Popped 5
[Consumer] Popped 6
[Consumer] Popped 7
[Consumer] Popped 8
[Consumer] Popped 9
3. 原因解释
下面我通过push()的运行流程来解释为什么不进行notify会导致worker无响应:
std::unique_lock<std::mutex> lock(mtx_);获取到了锁mtx_not_full_cv_.wait(lock, [&]{ return queue_.size() < capacity_; });的时候,std::condition_variabl会检测condition是否满足:- 如果满足:则继续执行接下来的代码(直到出作用域后,
std::unique_lock会自动把锁归还) - 如果不满足:则立刻原子地进行下面两件事情:
{把取得的锁归还, 阻塞当前线程}
- 如果满足:则继续执行接下来的代码(直到出作用域后,
因此,若条件queue_.size() < capacity不满足,则worker线程会立刻被阻塞,此后除非被假性唤醒,不然理论上该线程不会被唤醒,直到被其他线程notify。故而,不进行notify会导致worker无响应。

浙公网安备 33010602011771号