最近复习线程池源码时,产生以下疑问,记录一下。
条件变量判断时,何时用while,何时用if?
个人总结一下:
条件可能被多个线程改变,使用while;条件只被一个线程改变,使用if。
具体细节:
该代码片段为线程池中,功能为给用户提供的任务提交接口,即生产者线程生产任务。
void ThreadPool::submitTask(std::shared_ptr<Task> sp) { // 提交任务
std::unique_lock<std::mutex> lock(taskQueMtx_); // 获取锁
while (taskQue_.size() == taskQueMaxThreshold_) { // 任务队列满了的话
notFull_.wait(lock); // 则释放锁,等待不满。
}
// notFull_.wait(lock, [&]()->bool { // 等待任务队列不满
// return taskQue_.size() < taskQueMaxThreshold_;
// 任务队列任务数 < 阈值,则return true,条件成立,可继续往下执行。
// }); // wait()函数该重载形式,等价于上面while判断 + wait().
taskQue_.emplace(sp); // 不满则提交任务。
taskSize_++;
notEmpty_.notify_all(); // 通知消费者线程任务队列不空
}
第四行 为什么使用while,而不是if ?
线程池为生产者-消费者模型,存在多个生产者、消费者线程。多个生产者、消费者线程都有可能改变条件状态。
若使用if,会出现如下情况:
生产者线程A第一次进行if判断,发现taskQue_.size() == taskQueMaxThreshold_条件成立,则释放锁、阻塞在notFull_上;
后续消费者消费了n个(n>0)任务,使得taskQue_.size() < taskQueMaxThreshold_, 同时唤醒了被阻塞生产者线程A,线程A被唤醒后,仍需要获取锁后,才能继续往下执行;
若锁被生产者线B程抢走,线程B生产了n个(n>0)任务,此时taskQue_.size() == taskQueThreshold_条件再次成立,生产者线程B释放了锁;
此时生产者线程A抢到锁,线程A继续从上次被阻塞的地方(if判断)开始执行,因为是if判断,线程A上次已经判断过了,则不再判断,继续往下执行,导致taskQue_.size() = taskQueMaxThreshold时,线程A继续生产任务,使得任务队列中任务数超过最大任务数,出错。
若为while判断,则线程A被唤醒且抢到锁后,仍需while循环进行条件判断。此时while循环发现任务队列再次被其他生产者线程填满,则继续等待。
上述代码注意事项:
线程被唤醒后,会重新竞争锁。
得到锁后,则从wait()函数下一行继续执行,即继续进行while判断。