C++并发实战11:条件变量

1  线程睡眠函数

std::this_thread::sleep_for(std::chrono::milliseconds(100));//头文件#include<chrono>,供选择的如seconds()等
       不要使用睡眠函数同步线程,睡眠函数可以用于复现线程的一些行为。

    线程重新调度函数:

std::this_thread::yield()
重新调度本线程,用于线程等待其它线程时而不阻塞本线程


2  条件变量std::condition_variable不允许拷贝和移动的。其基本语义和Linux的pthread_cond_t差不多。

condition_variable();
condition_variable (const condition_variable&) = delete;//没有copy constructor和move constructor

~condition_variable();//阻塞在此条件变量上的线程将被唤醒,不会有线程再对此条件变量wait
void wait (unique_lock<mutex>& lck);//等待条件发生,注意参数是unique_lock,可能发生虚假唤醒,即不是notify唤醒的

template <class Predicate>
  void wait (unique_lock<mutex>& lck, Predicate pred);//pred是个函数对象返回bool,线程会在pred返回false的下阻塞,pred返回true会被唤醒,这样可以防止虚假唤醒等价于:while (!pred()) wait(lck);
template <class Rep, class Period>

  cv_status wait_for (unique_lock<mutex>& lck,const chrono::duration<Rep,Period>& rel_time);//在指定rel_time时间段内等待,此期间可能被notify唤醒
template <class Rep, class Period, class Predicate>
       bool wait_for (unique_lock<mutex>& lck,const chrono::duration<Rep,Period>& rel_time, Predicate pred);//为了防止虚假唤醒,加了一个pred,在rel_time内被notify且要pred返回true方可被唤醒

template <class Clock, class Duration>
  cv_status wait_until (unique_lock<mutex>& lck,const chrono::time_point<Clock,Duration>& abs_time);//等待到一个指定的绝对时间点,语义和wait_for差不多
template <class Clock, class Duration, class Predicate>
       bool wait_until (unique_lock<mutex>& lck,const chrono::time_point<Clock,Duration>& abs_time,Predicate pred);

void notify_one() noexcept;//若有多个线程等待此条件变量则选择一个线程唤醒,若没有线程等待则此函数什么也不做
void notify_all() noexcept;//唤醒所有阻塞在此条件变量上的线程,若没有阻塞线程此函数什么也不做

3  下面是一个使用condition_variable和queue实现的单生产者单消费者代码片段

std::mutex mut;//该mutex会被condition_variable使用
std::queue<data_chunk> data_queue;//用于在生产者线程和消费者线程间传递数据
std::condition_variable data_cond;
void data_preparation_thread()//生产者
{
   while(more_data_to_prepare())
   {
      data_chunk const data=prepare_data();
      std::lock_guard<std::mutex> lk(mut);//这里可以用lock_guard或者unique_lock
      data_queue.push(data);// 
      data_cond.notify_one();//唤醒一个阻塞在条件变量的线程,多个阻塞线程会选择一个唤醒,若没有阻塞的线程则什么也不做
   }
}
void data_processing_thread()//消费者
{
   while(true)//不断消费
   {
      std::unique_lock<std::mutex> lk(mut);//注意condition_varibale必须使用unique_lock,因为unique_lock提供了lock和unlock操作更加灵活 
      data_cond.wait(lk,[]{return !data_queue.empty();});//##1##lambda表达式用于检测queue是否为空,这里可以是任意的callable object
      data_chunk data=data_queue.front();
      data_queue.pop();
      lk.unlock();//wait返回时mutex处于locked,为了提高并发应该立即显示解锁
      process(data);//处理数据
      if(is_last_chunk(data))
         break;
   }
}
        ##1##处说明:

        wait首先检查lambda表示是否为真(queue是否为空),若为假(queue中有数据)wait直接返回;若为假则表明条件不满足,那么wait会将mutex解锁,并使线程进入阻塞状态,这里看出为什么要用unique_lock了吧因为其提供了比lock_guard更灵活的lock和unlock操作。

        当阻塞的线程被notify_one()/notify_all()唤醒时,首先对mutex上锁,并检查lambda表达式(queue是否为空的条件)。若条件满足(queue非空)则wait立即返回,mutex处于locked。若条件仍不满足(queue为空),wait将对mutex解锁,并继续阻塞线程在waiting状态。


4 测试wait前后mutex的状态

#include<iostream>
#include<thread>
#include<mutex>
#include<chrono>
#include<condition_variable>
using namespace std;
mutex m;
condition_variable cond;
int flag=0;
void producer(){
    this_thread::sleep_for(chrono::seconds(1));
    lock_guard<mutex> guard(m);
    flag=100;
    cond.notify_one();
    cout<<"notify..."<<endl;
}
void customer(){
    unique_lock<mutex> lk(m);
    if(m.try_lock())
        cout<<"mutex unlocked after unique_lock"<<endl;
    else
        cout<<"mutex locked after unique_lock"<<endl;//输出
    while(flag==0){
        cout<<"wait..."<<endl;
        cond.wait(lk);
    }
    if(m.try_lock())
        cout<<"mutex unlocked after wait"<<endl;
    else
        cout<<"mutex locked after wait"<<endl;//输出
    cout<<"flag==100? "<<flag<<endl;
}
int main(){
    thread one(producer);
    thread two(customer);
    one.join();
    two.join();
    return 0;
}

程序输出:

mutex locked after unique_lock        //unique_lock(mutex&)会拥有mutex并对mutex上锁
wait...
notify...
mutex locked after wait       //wait返回后mutex仍处于locked状态
flag==100? 100      



你可能感兴趣的:(C++并发实战11条件变量)