关于C++阻塞队列中push与pop线程未交替执行的疑问
嘿,我完全懂你的困惑——你本来以为push线程把元素塞进队列、解锁互斥量并通知条件变量后,pop线程会立刻醒过来执行,但结果却是push一口气把队列填满,之后pop才开始清空队列,完全没按你预想的“交替执行”来,对吧?咱们来掰扯清楚背后的原因。
首先要明确一个核心点:条件变量的notify只是“唤醒等待的线程”,但它管不了操作系统的线程调度器。
问题出在哪?
线程调度的“惰性”:当push线程调用
notify_one()之后,它并没有立刻放弃CPU控制权。操作系统的调度器通常会让当前正在运行的线程继续执行,直到它的时间片用完,或者遇到阻塞操作(比如wait)。如果你的push线程是在一个循环里不断调用push函数,那它解锁mutex后,很可能会立刻再次尝试获取mutex——因为它就在现场,调度器大概率会把mutex再次分配给它,而不是切换到刚被唤醒的pop线程。被唤醒的pop线程还要抢mutex:就算pop线程被notify唤醒了,它也得先拿到mutex才能访问队列。但这时候push线程可能已经重新抢到了mutex,继续往队列里塞元素,直到队列被填满,push线程触发wait条件(队列满了),这时候它才会释放mutex并进入阻塞状态,调度器才会切换到pop线程开始工作。
可能你的代码里pop后没notify?(不过从你的描述看,主要问题是push没让pop有机会执行,但还是提一句)如果pop线程取出元素后没有notify push线程,那当队列被清空后,pop线程会阻塞,而push线程可能还在等notify——不过这是另一种情况了。
怎么让它们更可能交替执行?
你没法强迫调度器严格交替,但可以通过一些操作增加交替的概率:
- 在push/pop后主动让出CPU:调用
std::this_thread::yield(),告诉调度器“我现在可以歇会,让别的线程跑吧”。这会大大提高pop线程被调度的概率,但也不是100%保证。 - 确保push和pop都正确调用notify:push完元素要notify等待的pop线程,pop完元素要notify等待的push线程,这是阻塞队列的基本操作,保证两边都能及时收到信号。
修正后的代码示例
给你贴个完整的阻塞队列实现,加上了yield和正确的notify逻辑:
#include <iostream> #include <thread> #include <queue> #include <mutex> #include <condition_variable> using namespace std; template<typename E> class BlockingQueue { private: size_t mMaxSize; queue<E> mQueue; mutex mtx; condition_variable mConditionVar; public: BlockingQueue(size_t size): mMaxSize{size} {} void push(E element) { unique_lock<mutex> pushLock(mtx); // 等待队列有空闲位置 mConditionVar.wait(pushLock, [this](){ return mQueue.size() < mMaxSize; }); mQueue.push(element); cout << "Pushed: " << element << endl; // 提前解锁(可选,unique_lock离开作用域也会自动解锁) pushLock.unlock(); // 通知pop线程有新元素了 mConditionVar.notify_one(); // 主动让出CPU this_thread::yield(); } E pop() { unique_lock<mutex> popLock(mtx); // 等待队列非空 mConditionVar.wait(popLock, [this](){ return !mQueue.empty(); }); E element = mQueue.front(); mQueue.pop(); cout << "Popped: " << element << endl; popLock.unlock(); // 通知push线程有空位了 mConditionVar.notify_one(); this_thread::yield(); return element; } }; int main() { BlockingQueue<int> bq(5); thread pushThread([&bq](){ for(int i=0; i<10; ++i){ bq.push(i); } }); thread popThread([&bq](){ for(int i=0; i<10; ++i){ bq.pop(); } }); pushThread.join(); popThread.join(); return 0; }
运行这个代码,你会看到push和pop交替执行的概率大大提高——不过还是要记住,线程调度的行为是不确定的,极端情况下还是可能出现连续push或连续pop的情况,这是操作系统的特性,没法完全避免。
备注:内容来源于stack exchange,提问作者Rudransh Srivastava

