C++11: std::condition_variable

需要特别注意的是: std::condition_variable最好配合std::unique_lock使用!!!!!!!!

有时候,被不同线程执行的task必须彼此等待。所以对于“并发操作”来说同步化除了防止data race之外还有其他原因。

你可能会争辩说我们已经引入了Future允许我们停下来直到另外一个线程结束提供数据,但是这样等待一个线程结束才能提供数据要经历一个函数的过程呀!浪费了很多时间,而且Future对象只能get()一次结果。

Condition Variable(条件变量)的意图:

bool ready_flag;
std::mutex mutex;


//等待直到ready_flag被设置为true;
{
   std::unique_lock<std::mutex> unique_l(mutex);
   while(!ready_flag){                         
        unique_l.unlock();                           
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        unique_l.lock(); 
      }
}

但是上面的轮询等待条件的具备通常不是最好的做法(虽然是合理的也没有问题的)。

等待中的线程消耗宝贵的CPU时间重复检验ready_flag,且当它锁住mutex时候“负责设置ready_flag为true"的那个线程会被阻塞(blocking)...而且上面的代码让我们很难找到适当的sleep周期。

#include <iostream>
#include <future>
#include <condition_variable>
#include <thread>
#include <mutex>

std::mutex mutex;
bool ready_flag = false;
std::condition_variable ready_condition;

void thread_one()
{
	std::cout << " <retun> " << std::endl;
	std::cin.get();

	{
		std::lock_guard<std::mutex> lock_g(mutex);
		ready_flag = true;
	}

	ready_condition.notify_one(); //注意这里.
}

void thread_two()
{
	{
		std::unique_lock<std::mutex> unique_l(mutex);
		ready_condition.wait(unique_l, []()->bool { return ready_flag; });
	}

	std::cout << "done(完成)" << std::endl;
}

int main()
{
	std::future<void> result_one = std::async(std::launch::async, thread_one);
	std::future<void> result_two = std::async(std::launch::async, thread_two);

	return 0;
}

在包含必要的头文件之后,我们还需要三个条件以保证在线程之间通讯.

1,一个用以表示条件真的满足了的flag(也就是此处的ready_flag).

2,一个mutex(也就是上面代码中的std::mutex mutex);

3,一个condition variable(也就是上面代码中的ready_condition);

再来一个例子:

#include <iostream>
#include <future>
#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>

std::condition_variable ready_condition;
std::mutex mutex;
std::queue<int> queue;

void provider(const int& val)
{
	std::cout << "test1" << std::endl;
	//把不同的值放到std::queue中去.
	for (int i = 0; i < 6; ++i) {
		{
			std::lock_guard<std::mutex> lock_g(mutex); //这里确保了queue的读写是atomic(不可分割的).
			queue.push(val + i);
		}

		ready_condition.notify_one(); //每次quque中加入新元素都会唤醒一个线程输出该元素.
		//还有注意上面这条并不在保护区内,也不需要在保护区内.虽然std::queue是的读写操作不能同时进行
		//但是当保护区结束的时候push()操作已经完成了.而对std::queue执行读操作在另外线程的时候也是被上锁的因此不可能会产生data race.

		std::this_thread::sleep_for(std::chrono::milliseconds(val));//休眠一段时间保证consumer运行.不然notify完了没有wait的什么事也不会发生.
		//因为我们的consumer()在main()函数中是稍微落后一点provider()的.
	}
}

void consumer(const int& number)
{
	std::cout << "test2" << std::endl;
	while (true) {
		int val;
		{
			std::unique_lock<std::mutex> unique_l(mutex);
			ready_condition.wait(unique_l, []()->bool { return !queue.empty(); });
			val = queue.front();
			queue.pop();
		}

		std::cout << "consumer " << number << ": " << val << std::endl;
	}
}

int main()
{
	std::future<void> result_one = std::async(std::launch::async, provider, 100);
	std::future<void> result_two = std::async(std::launch::async, provider, 200);
	std::future<void> result_three = std::async(std::launch::async, provider, 300);

	std::future<void> result_four = std::async(std::launch::async, consumer, 1);
	std::future<void> result_five = std::async(std::launch::async, consumer, 2);

	return 0;
}

 

class std::condition_variable

构造函数:

condition_variable();
	
condition_variable (const condition_variable&) = delete;

构造一个std::condition_variable对象,该对象不能被copy,也不能被移动.

成员函数部分:

std::condition_variable::wait

这里需要指出的是wait()和notify_one()或者notify_all()必须成对使用.

void wait (unique_lock<mutex>& lck);
	
template <class Predicate>
  void wait (unique_lock<mutex>& lck, Predicate pred);

该函数在解锁他所管理的mutex的情况下阻塞(block)正在调用wait()的线程(此时正处于阻塞状态)直到其他线程中的notify_one()或者notify_all()被调用,再次lock他管理的Mutex.

需要注意的是,std::condition_variable的wait()总是在已经被上锁的mutex的基础上执行操作的.wait()会指向下面三个操作暂时解锁当前线程已经锁住的mutex:

1,解锁(unlock)mutex然后使当前线程进入等待状态.

2,收到notify信号后解除等待状态.

3,再次锁住mutex,由前面std::unique_lock来决定何时解.

还有一点需要注意的是:wait系列的函数都可以再接受一个callable object,用以判断条件是否满足,同时防止处于等待状态的线程假醒.

比如在上面的第一个例子中:

ready_condition.wait(unique_l, []()->bool { return ready_flag; });

其实相当于:

{
  std::unique_lock<std::mutex> unique(mutex);
  while(!ready_flag){
     unique.unlock();
     ready_condition.wait(unique_l);
     unique.lock();
}

 

我们再来看一个更加详细的例子:

#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>
 
std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;
 
void waits()
{
    std::unique_lock<std::mutex> lk(cv_m);
    std::cout << "Waiting... \n";
    cv.wait(lk, []{return i == 1;});
    std::cout << "...finished waiting. i == 1\n";
    done = true;
}
 
void signals()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Notifying falsely...\n";
    cv.notify_one(); // waiting thread is notified with i == 0.
                     // cv.wait wakes up, checks i, and goes back to waiting
 
    std::unique_lock<std::mutex> lk(cv_m);
    i = 1;
    while (!done) 
    {
        std::cout << "Notifying true change...\n";
        lk.unlock();
        cv.notify_one(); // waiting thread is notified with i == 1, cv.wait returns
        std::this_thread::sleep_for(std::chrono::seconds(1));
        lk.lock();
    }
}
 
int main()
{
    std::thread t1(waits), t2(signals);
    t1.join(); 
    t2.join();
}

可能Output结果:

Waiting... 
Notifying falsely...
Notifying true change...
...finished waiting. i == 1

 

std::condition_variable::wait_for

template <class Rep, class Period>
  cv_status wait_for (unique_lock<mutex>& lck,
                      const chrono::duration<Rep,Period>& rel_time);
	
template <class Rep, class Period, class Predicate>
       bool wait_for (unique_lock<mutex>& lck,
                      const chrono::duration<Rep,Period>& rel_time, Predicate pred);

该函数在解锁(unlock)当前线程已经上锁(lock)mutex的情况下阻塞(block)当前线程一段时间(duration),直到其他线程中的notify_one()或者notify_all()被调用,或者超过了这段时间也没有收到来自其他线程的notify_one()(或者是notify_all())那么该函数都会返回一个std::cv_status(稍后会介绍)类型的参数.

请注意:std::condition_variable::wait_for也是可以接受一个callable object.其效果更wait()接受一个callable object类似只不过是在给定的时间段内.最好这么做能够防止假醒.

std::condition_variable::wait_until

template <class Clock, class Duration>
  cv_status wait_until (unique_lock<mutex>& lck,
                        const chrono::time_point<Clock,Duration>& abs_time);

template <class Clock, class Duration, class Predicate>
       bool wait_until (unique_lock<mutex>& lck,
                        const chrono::time_point<Clock,Duration>& abs_time,
                        Predicate pred);

该函数在解锁(unlock)当前线程已经上锁(lock)了的mutex的情况下阻塞(block)当前线程到某个时间点。如果其他线程中的notify_one()(或者是notify_all())在该时间点之前到达之前被调用了那么返回一个std::cv_status类型的对象,如果超过了该指定的时间点也没有收到任何来自notify_one()或者notify_all()的信号也返回一个std::cv_status类型的对象.

std::condition_variable::notify_one

void notify_one() noexcept;

唤醒(weak)某个正在等待该信号的线程,如果有多个线程都在等待该信号的话具体是哪个会被唤醒(weak)是没法指定的.如果没有任何线程等待该型号那么什么事情也不做.

std::condition_variable_notify_all

void notify_all() noexcept;

唤醒(weak)所有的正在等待信号的线程,如果没有等待者什么都不做.

std::cv_status

enum class cv_status;
cv_status::timeout //超时的话返回这个.
cv_status::no_timeout //没有超时.

 

转载于:https://my.oschina.net/SHIHUAMarryMe/blog/679163

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值