需要特别注意的是: 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 //没有超时.