基于Mutex的C++ Blocking Queue实现错误排查求助
仅用Mutex实现C++阻塞队列的代码问题分析
背景
我近期开始学习并发编程,尝试仅使用Mutex实现C++阻塞队列以完成生产者-消费者示例,尚未接触信号量或读写锁,以下是我的实现代码,请帮忙审查指出错误及原因:
// Thread-safe Queue template<class T> class BlockingQueue { public: BlockingQueue() { isempty.lock(); } void push(T obj) { std::lock_guard<std::mutex> lk(mtx); data.push(obj); isempty.unlock(); // Queue is no longer empty } T pop() { isempty.lock(); // Acquired lock means I queue is not empty -- or wait until // Calling isempty first so that there is no deadlock if pop is blocked on this // and meanwhile push is called my another thread mtx.lock(); auto x = data.front(); data.pop(); if(!empty()) { mtx.unlock(); isempty.unlock(); // Queue is still not empty } else { mtx.unlock(); // Queue is empty don't unlock "isempty" } return x; } private: bool empty() { return data.empty(); } size_t size() { return data.size(); } std::queue<T> data; std::mutex mtx; // Mutex to stop data-race std::mutex isempty; // Mutex to block on empty Queue };
代码中的错误及原因
1. isempty锁的核心逻辑完全错误
- 构造函数中直接调用
isempty.lock():队列初始化时本身为空,此时持有锁会导致所有消费者线程一开始就被阻塞,且后续push中的unlock操作违反互斥锁规则——只有持有锁的线程才能调用unlock,无锁状态下调用unlock属于未定义行为,会引发程序崩溃或异常。 - 多消费者场景失效:当队列中有多个元素时,一个
push只能unlock一次,仅能唤醒一个消费者线程,其余消费者会持续阻塞,无法处理队列剩余元素。 - 空状态维护逻辑颠倒:队列被
pop至空时,代码保留isempty锁不释放,但后续push调用unlock时,若此时无线程持有该锁,会触发未定义行为,且后续消费者会一直阻塞,即使队列已被重新填充。
2. 锁顺序错误引发死锁风险
pop函数中先锁isempty再锁mtx,若存在其他线程(如错误操作)先锁mtx再尝试锁isempty,会出现死锁——两个线程互相持有对方需要的锁,无限等待。
3. 数据竞争导致无效状态判断
pop中调用empty()时未持有mtx锁,data.empty()并非线程安全操作,此时其他线程可能正在修改data,导致empty()的结果无效,进而引发后续逻辑错误(如队列实际为空时调用data.front(),触发未定义行为)。
4. 手动解锁存在异常安全问题
pop中手动调用mtx.unlock(),若data.front()或data.pop()抛出异常,mtx会一直被持有,导致其他线程无法访问队列,违反异常安全原则。应使用std::lock_guard或std::unique_lock自动管理锁的释放,避免手动解锁的风险。
5. 竞态条件引发错误唤醒
pop中判断!empty()后再解锁isempty,但在判断与解锁之间,可能有其他线程执行pop操作让队列变为空,此时解锁isempty会唤醒不必要的消费者线程,这些线程拿到锁后发现队列为空,会触发data.front()的未定义行为。
修正后的简化实现(仅用Mutex)
如果坚持只用Mutex实现,可通过循环检查队列状态的方式模拟阻塞逻辑,避免上述问题:
#include <queue> #include <mutex> #include <utility> // 用于std::move template<class T> class BlockingQueue { public: BlockingQueue() = default; void push(T obj) { std::lock_guard<std::mutex> lk(mtx); data.push(std::move(obj)); // 唤醒一个等待的线程 wait_lock.unlock(); } T pop() { while (true) { wait_lock.lock(); std::lock_guard<std::mutex> lk(mtx); if (!data.empty()) { auto x = std::move(data.front()); data.pop(); // 队列还有元素,解锁让其他消费者可以进入 if (!data.empty()) { wait_lock.unlock(); } return x; } wait_lock.unlock(); } } private: std::queue<T> data; std::mutex mtx; // 保护队列数据的互斥锁 std::mutex wait_lock; // 模拟等待/通知的锁,初始为解锁状态 };
内容的提问来源于stack exchange,提问作者Schon
相关产品推荐
相关产品推荐

