关于《C++程序设计语言》中条件变量实现生产者-消费者队列的技术问询
《C++程序设计语言》中条件变量实现生产者-消费者队列的技术问询
嘿,我刚好在Stroustrup的*《C++程序设计语言》*里看到过这个经典的生产者-消费者队列实现,用条件变量来协调线程间的同步,给你拆解一下这段代码的核心逻辑和细节:
首先,先明确几个核心的共享组件:
- Message类:这是我们要在线程间传递的消息载体,代码里省略了具体实现,你可以根据业务需求添加数据成员和处理方法。
queue<Message> mqueue:用来存储消息的缓冲区队列,是生产者和消费者共享的资源。condition_variable mcond:条件变量,是线程间的“通知器”,用来在队列状态变化时(比如从空变为有消息)唤醒等待的线程。mutex mmutex:互斥锁,用来保护对共享队列的所有操作,避免多个线程同时读写导致数据竞争和不一致。
生产者线程代码
生产者的逻辑很简单:持续生成消息,然后安全地把消息放进队列,再通知消费者有新消息了。
class Message { // 线程间传递的消息对象 //... 可自定义成员和方法 }; queue<Message> mqueue; // 消息队列 condition_variable mcond; // 同步用的条件变量 mutex mmutex; // 互斥锁 void producer() { while(true) { Message m; //... 填充消息内容 ... unique_lock<mutex> lck {mmutex}; // 加锁,确保对队列的操作是线程安全的 mqueue.push(m); mcond.notify_one(); // 唤醒一个等待中的消费者线程 } // 函数结束时,unique_lock自动释放锁 }
这里用unique_lock而不是lock_guard的原因是:条件变量的wait方法需要灵活地解锁和重新加锁,unique_lock支持这种操作,而lock_guard是一次性的加锁解锁,做不到这点。notify_one()只会唤醒一个等待的消费者,如果有多个消费者线程,也可以用notify_all(),不过notify_one()在单消费者场景下更高效。
消费者线程代码
消费者的核心是等待队列中有消息,然后取出并处理:
void consumer() { while(true) { unique_lock<mutex> lck{mmutex}; // 先获取互斥锁 // 等待,直到队列非空;这里的lambda是防止虚假唤醒的关键 mcond.wait(lck,[this]{ return !mqueue.empty(); }); Message m = mqueue.front(); mqueue.pop(); lck.unlock(); // 可以提前解锁,避免处理消息时占用锁,提升并发效率 //... 处理拿到的消息m ... } }
重点说一下mcond.wait()的作用:
- 调用
wait时,它会自动释放持有的互斥锁,然后让线程进入等待状态,避免占用锁导致生产者无法写入。 - 当生产者调用
notify_one()后,这个线程会被唤醒,然后重新获取互斥锁。 - 唤醒后会立刻检查lambda表达式的条件(队列是否非空)——这一步是为了应对虚假唤醒(线程可能被莫名唤醒,但此时队列还是空的),只有条件满足时才会继续执行,否则会再次进入等待。
额外注意事项
- 虚假唤醒:千万不能省略lambda条件检查,必须确保队列确实有消息后再进行取消息操作,否则会出现未定义行为。
- 锁的范围:处理消息的代码不需要持有锁,所以可以提前调用
lck.unlock(),让生产者能更快地获取锁写入消息,提升整体的并发性能。 - 线程安全:所有访问
mqueue的操作(包括检查空、push、pop)都必须在持有mmutex的情况下进行,否则会出现数据竞争。
备注:内容来源于stack exchange,提问作者Mathai
相关产品推荐
相关产品推荐

