cond.wait()为什么要使用mutex变量来保持同步

cond.wait()为什么要使用mutex变量来保持同步

本文将通过一个线程池实例来解释cond.wait()为什么需要使用mutex

代码如下,线程池使用生产者和消费者模式,使用一个队列来存储任务,addTask()函数供外部调用不断往队列中生产任务,子线程不断在队列中处理(消费)任务。其中使用condition_variable变量和mutex变量来保证互斥与同步。

#include "ThreadPool.h"
#include <iostream>

using JobFunc = std::function<void()>;


int ThreadPool::addTask(JobFunc&& func){
	std::unique_lock<std::mutex>lock(mx_);
	int ret = tasks_.push(func);
	if(ret > 0){
		std::cout << "添加任务成功,当前任务数为: "<< getTaskNum() << std::endl;
		cond_.notify_one();
	}
	return ret;
}

void ThreadPool::start(){
	if(!processing_){
		std::unique_lock<std::mutex>lock(mx_);
		processing_ = true;

		threads_.reserve(numWorkers_);
		for(int i=0;i<numWorkers_;i++){
			threads_.emplace_back(new std::thread(std::bind(&ThreadPool::run,this)));
		}
	}

}

void ThreadPool::run(){
	while(processing_){
		//std::cout << "run" << std::endl;
		JobFunc jf;
		std::unique_lock<std::mutex>lock(mx_);
		{
			while(processing_ && tasks_.getTaskNum() == 0){
				cond_.wait(lock);
			}
			if(!processing_ && tasks_.getTaskNum() == 0){
				return;
			}
			jf = tasks_.pop();
		}
		if(jf)
			jf();
	}
}

void ThreadPool::stop(){
	{
		std::unique_lock<std::mutex>lock(mx_);
		processing_ = false;
	}

	cond_.notify_all();//通知所有线程当前线程池要停止了,不要再接收新的任务.

	for(auto & th:threads_){
		std::cout << "线程 " << th->get_id() << "销毁" << std::endl;
		th->join();
		if(th != nullptr){
			delete th;
			th = nullptr;
		}
	}

	std::cout << "当前线程已经全部销毁" << std::endl;

}

int ThreadPool::getTaskNum(){
	return tasks_.getTaskNum();

ThreadPool::run()在使用cond_.wait()时结合了mutex变量,原因有3点:

1、首先要清楚的是,在进行while(processing_ && tasks_.getTaskNum() == 0)判断之前已经对muex加锁,在陷入到wait()内之后,会将当前线程阻塞,前面这一段时间的加锁,就是为了保证当前线程操作的原子性,如果当前线程为B,判断队列为空进入了while()循环内但是还没有陷入wait,而正当此时,一个线程A运行addTask()添加了新的任务,并notify_on(),但是此时B并没有阻塞,所以B会错过被唤醒。

2、当前线程陷入wait阻塞后,需要释放mutex,只有这样才能够保证addTask()获得到锁,才能够有机会notify()阻塞的线程。所以wait()必须获得mutex的使用权,并在阻塞后解锁。

3、当阻塞的线程被唤醒后,wait在返回前会先对mutex加锁,这也是wait()必须获得mutex使用权的原因之一,如果没有对mutex加锁,很可能出现线程虽然被唤醒,但是任务队列中已经空了的情况,造成后面的空指针错误。纵然是加了while(processing_ && tasks_.getTaskNum() == 0)循环,判断条件显示队列已经不为空,但是从判断完,到执行后面的语句(tasks_pop())存在临界区,比如在单核多线程的情况下,线程D在执行完while(processing_ && tasks_.getTaskNum() == 0),显示队列不为空跳出循环,此时当前线程的时间片已经用完,被挂起,而另一个线程C获得了CPU使用权,并将队列中唯一的一个任务拿走(tasks_pop()),当D再次获得时间片运行tasks_.pop()时,就会出现空指针错误。

总结来讲:mutex保证了多线程对共享数据段的互斥访问,cond保证了线程之间的快速响应;在进行变量判断前需要加锁,wait()将当前=线程放入到阻塞队列后要自动解锁,当被唤醒时需要先加锁,然后判断条件。另外,在进行notify之前需要加锁,notify()之后需要解锁。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值