条件变量

使用条件变量的最经典的场景就是生产者和消费者

1.最普通的生产者和消费者代码

#include<iostream>
#include<deque>
#include<thread>
#include<mutex>
#include<functional>

using namespace std;

std::deque<int> Q;
std::mutex mu;

void producers() {
	int count = 10;
	while (count > 0) {
		std::unique_lock<std::mutex> locker(mu,std::defer_lock);
		locker.lock();
		Q.push_front(count);
		locker.unlock();
		std::this_thread::sleep_for(chrono::seconds(1));
		count--;
	}
}

void consumers() {
	int data = 0;
	while (data!=1)
	{
		std::unique_lock<std::mutex> locker(mu);
		if (!Q.empty()) {
			data = Q.back();
			Q.pop_back();
			locker.unlock();
			std::cout << "consumer get data :" << data << endl;
		}
		else {
			locker.unlock();
		}
	}
}

int main(int argc, char** argv) {
	std::thread t1(producers);
	std::thread t2(consumers);
	t1.join();
	t2.join();
	return 0;
}

这里的代码有个问题就是consumers()中while循环中,如果Q为空的时候,就会不停的执行else{ locker.unlock();}。这是相当资源的。那么我们对这段代码进行
改进下。

2.改进1,让consumer等待

consumers在进行一次Q判断为空的情况,让线程休息一下,这里我们修改一下consumers().

void consumers() {
	int data = 0;
	while (data!=1)
	{
		std::unique_lock<std::mutex> locker(mu);
		if (!Q.empty()) {
			data = Q.back();
			Q.pop_back();
			locker.unlock();
			std::cout << "consumer get data :" << data << endl;
		}
		else {
			locker.unlock();
			std::this_thread::sleep_for(chrono::microseconds(5));
		}
	}
}

这里我们添加这行代码

std::this_thread::sleep_for(chrono::microseconds(5));

让线程休息5秒

但是我们仔细想想,这样合理吗,可能有更好的解决方案,比如生产者生产出来一个产品,再通知消费者,不就可以解决消费者循环等待的问题了嘛。

4.条件变量

生产者生产出来一个产品,再通知消费者,不就可以解决消费者循环等待的问题。利用这个思想代码就可以写成这样

#include<condition_variable>
condition_varible cond;

这里是引入条件变量的头文件和创建一个全局的条件变量

void producers() {
	int count = 10;
	while (count > 0) {
		std::unique_lock<std::mutex> locker(mu,std::defer_lock);
		locker.lock();
		Q.push_front(count);
		locker.unlock();
		cond.notify_one();

		std::this_thread::sleep_for(chrono::seconds(1));
		count--;
	}
}

这里在生产者生产了一个产品后,使用cond.notify_one(),来激活一个消费者。如果有多个消费者可以notify_all()

void consumers() {
	int data = 0;
	while (data != 1)
	{
		std::unique_lock<std::mutex> locker(mu);
		cond.wait(locker);
		data = Q.back();
		Q.pop_back();
		locker.unlock();
		std::cout << "consumer get data :" << data << endl;
	}
}

消费者要一直wait(),直到生产者告诉它产品生产好了。

当然这里代码也是有问题的,会不会出现伪激活的情况呢,道理简单就是Q不为空时使用,就是前提。可以写成一个lamda函数。
怎么理解这里呢, 就是wait的时候,如果只有一个locker,那么如果当前进程拿到locker就往下进行了,实际上并不满足向下运行的条件。
所以要wait进行限制,这里我忘记这个叫什么了,是叫什么“预测”吧。实际上写得就是一个lamda函数,可以理解成一个if条件,就是符合情况,向下进行的条件。

所以我们还要对cond.wait(locker)进行修改

//void wait( std::unique_lock<std::mutex>& lock, Predicate pred );
cond.wait(locker, []() {return !Q.empty(); });

注意

在面向对象编程的时候,如果使用到条件变量,一定要注意wait和析构的配合。 怎么说呢?就是加入我对象类有个资源的使用会使用到条件变量,而在使用过程某个
线程正在wait资源,而我马上就要析构这个对象了,那不就是出现bug了吗。所以说当析构的时候,一定要通知,wait条件满足赶快释放资源。
常见的操作就是,申请对象的时候将对象设置了running状态,然后在wait哪里的条件的加上判断这个对象是否处于runing状态。我写个伪代码吧

class A{
public:
 A(){
   status_ = true; //标志这个instance 存活
 }
 ~A() {
   status_ = false;
   cond.notify_all(); // 告诉所有在等待的wait的赶快通过,马上要释放资源了。
   // 释放资源
 }
 bool Process() {
  // ... 某处waity
  cond.wait(locker,[](){ return !status || 具体条件;});
}
private:
 std::atomic<bool> status_{false};
 condition_varible cond;
};

关于条件变量还有很多细节可以自己去查阅一下

posted @ 2020-08-02 23:03  cyssmile  阅读(264)  评论(0编辑  收藏  举报