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

2014-11-24 03:23:25 · 作者: · 浏览: 0

1 线程睡眠函数

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


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
  
   & lck);//等待条件发生,注意参数是unique_lock,可能发生虚假唤醒,即不是notify唤醒的

template 
   
     void wait (unique_lock
    
     & lck, Predicate pred);//pred是个函数对象返回bool,线程会在pred返回false的下阻塞,pred返回true会被唤醒,这样可以防止虚假唤醒等价于:while (!pred()) wait(lck); template 
     
       cv_status wait_for (unique_lock
      
       & lck,const chrono::duration
       
        & rel_time);//在指定rel_time时间段内等待,此期间可能被notify唤醒 template 
        
          bool wait_for (unique_lock
         
          & lck,const chrono::duration
          
           & rel_time, Predicate pred);//为了防止虚假唤醒,加了一个pred,在rel_time内被notify且要pred返回true方可被唤醒 template 
           
             cv_status wait_until (unique_lock
            
             & lck,const chrono::time_point
             
              & abs_time);//等待到一个指定的绝对时间点,语义和wait_for差不多 template 
              
                bool wait_until (unique_lock
               
                & lck,const chrono::time_point
                
                 & 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_queue;//用于在生产者线程和消费者线程间传递数据
std::condition_variable data_cond;
void data_preparation_thread()//生产者
{
   while(more_data_to_prepare())
   {
      data_chunk const data=prepare_data();
      std::lock_guard
   
     lk(mut);//这里可以用lock_guard或者unique_lock data_queue.push(data);// data_cond.notify_one();//唤醒一个阻塞在条件变量的线程,多个阻塞线程会选择一个唤醒,若没有阻塞的线程则什么也不做 } } void data_processing_thread()//消费者 { while(true)//不断消费 { std::unique_lock
    
      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
  
   
#include
   
     #include
    
      #include
     
       #include
      
        using namespace std; mutex m; condition_variable cond; int flag=0; void producer(){ this_thread::sleep_for(chrono::seconds(1)); lock_guard
       
         guard(m); flag=100; cond.notify_one(); cout<<"notify..."<
        
          lk(m); if(m.try_lock()) cout<<"mutex unlocked after unique_lock"<
         
          
程序输出:

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