c++ 學習之 多線程(八)條件變量condition_variable

condition_variable成員函數

condition_variable 是 c++11 提供的一個可以實現線程同步的類。下面來總結一下條件變量condition_variable的用法。

1. condition_variable::wait()

成員函數wait()需要與unique_lock搭配使用。wait()函數有兩種調用方法。

(一)void wait (unique_lock& lck);

只有一個參數,傳入一個unique_lock對象就可以了。 condition_variable對象調用wait ()函數後,該線程會休眠,並且釋放unique_lock對象所佔有的鎖。直到同一對象調用 notify_one() 或者 notify_all() ,該線程會被喚醒,並且不斷去獲取unique_lock對象的鎖,直到獲取成功,函數返回。

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

需要兩個參數,第一個參數傳入一個unique_lock對象,第二個參數需要傳入一個可調用對象(函數,lambda表達式等)。調用wait()函數時,會先拿到unique_lock的鎖,接着判斷可調用對象的返回值是否爲true,若爲true函數返回,否則釋放掉unique_lock對象所佔有的鎖,然後該線程休眠,直到同一對象調用 notify_one() 或者 notify_all() ,該線程會被喚醒,然後不斷去獲取unique_lock對象的鎖,直到獲取成功再次判斷可調用對象的返回值真假,爲true函數返回,爲false再次休眠,直到下一次喚醒。

2.condition_variable::notify_one()

condition_variable對象調用notify_one()來喚醒任意一個被該對象wait()函數阻塞的線程。如果沒有線程被該對象wait()函數阻塞,就什麼也不做。
注意,只能喚醒同一對象的wait函數,而且是任意一個,當有多個線程線程被該對象wait()函數阻塞時,無法確定是哪一個被喚醒。

3. condition_variable::notify_all()

和condition_variable::notify_one()功能一致,區別在於condition_variable::notify_all()可以喚醒所有的被該對象阻塞的線程。

還有成員函數 condition_variable::wait_for()和condition_variable::wait_until()這裏不做詳細解釋。

condition_variable 的使用

首先看一下用雙重檢查模擬多線程的隊列緩衝區維護。

#include<thread>
#include<mutex>
#include<queue>
#include<stdio.h>
using namespace std;
queue<int>qu;
condition_variable my_cond;
mutex m;
void PushQueue()
{
  for(int i=0;i<10000;i++)
  {
    unique_lock<mutex>m_lock(m);
    qu.push(i);
  }
}

void PopQueue()
{
   while(true)
   {
     if(!qu.empty())
     {
	  unique_lock<mutex>m_lock(m);
	  f(!qu.empty())
	   {
	        qu.pop();
	   }
      }
   }
}
int main()
{
 thread t1(PushQueue);
 thread t2(PopQueue);
 t1.join();
 t2.join();
}

在多線程的開發中,常常用到一種 生產者——消費者模式,將功能模塊化,一個模塊負責生產消息,一個模塊負責處理數據,這樣二者之間就要有一個緩衝區來作爲橋樑。負責生產的模塊將消息放入緩衝隊列,負責處理的模塊從緩衝隊列中獲取消息。
那麼這個緩衝的隊列作爲二者的臨界區,就必須保證其線程安全,使線程同步。以雙重檢查的機制來確保消費者模塊可以從緩衝隊列中安全的取到消息,無論是功能上還是效率上都是比較不錯的。同樣使用條件變量condition_variable,也可以實現這一功能。

#include<thread>
#include<mutex>
#include<queue>
#include<stdio.h>
using namespace std;
queue<int>qu;
condition_variable my_cond;
mutex m;
void PushQueue()
{
 for (int i = 0; i < 1000; i++)
 {
  unique_lock<mutex>m_lock(m);
  qu.push(i);
  m_lock.unlock();
  my_cond.notify_one();
    }
}
void PopQueue()
{
 
 while (true)
 {
  unique_lock<mutex>m_lock(m);
  my_cond.wait(m_lock, [=] {
   if (qu.empty())
    return false;
   return true;
   });
  qu.pop();
 }
}
int main()
{
 thread t1(PushQueue);
 thread t2(PopQueue);
 t1.join();
 t2.join();
}

最後還有一個需要注意的地方,只有當消費者線程被wait()阻塞時,生產者線程調用notify_one纔會有用,所以當消費者線程被喚醒後,繼續執行下面代碼時,生產者調用的notify_one就不會發生效果,但是生產者線程每次都只會調用一次notify_one,所以消費者線程實際上被喚醒的次數不一定等於生產者調用notify_one的次數,每次喚醒只取一條會導致緩衝隊列中的消息積壓,造成消息無法及時處理,需要根據實際情況對這部分進行處理優化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章