C++11併發編程-條件變量(condition_variable)詳解

        C++標準庫線程頭文件《thread》主要包含了與條件變量相關的類和函數。相關的類包括 std::condition_variable和 std::condition_variable_any,還有枚舉類型std::cv_status。另外還包括函數 std::notify_all_at_thread_exit(),下面分別介紹一下以上幾種類型。

https://en.cppreference.com/w/cpp/thread

https://en.cppreference.com/w/cpp/thread/condition_variable/notify_one

std::condition_variable 類介紹:

std::condition_variable是條件變量,更多有關條件變量的定義參考維基百科。Linux下使用 Pthread庫中的 pthread_cond_*() 函數提供了與條件變量相關的功能, Windows 則參考 MSDN

當 std::condition_variable對象的某個wait 函數被調用的時候,它使用 std::unique_lock(通過 std::mutex) 來鎖住當前線程。當前線程會一直被阻塞,直到另外一個線程在相同的 std::condition_variable 對象上調用了 notification 函數來喚醒當前線程。

std::condition_variable 對象通常使用 std::unique_lock 來等待,如果需要使用另外的 lockable 類型,可以使用std::condition_variable_any類,本文後面會講到 std::condition_variable_any 的用法。

#include <iostream>                // std::cout
#include <thread>                // std::thread
#include <mutex>                // std::mutex, std::unique_lock
#include <condition_variable>    // std::condition_variable
 
std::mutex mtx; // 全局互斥鎖.
std::condition_variable cv; // 全局條件變量.
bool ready = false; // 全局標誌位.
 
void do_print_id(int id)
{
    std::unique_lock <std::mutex> lck(mtx);
    while (!ready) // 如果標誌位不爲 true, 則等待...
        cv.wait(lck); // 當前線程被阻塞, 當全局標誌位變爲 true 之後,
    // 線程被喚醒, 繼續往下執行打印線程編號id.
    std::cout << "thread " << id << '\n';
}
 
void go()
{
    std::unique_lock <std::mutex> lck(mtx);
    ready = true; // 設置全局標誌位爲 true.
    cv.notify_all(); // 喚醒所有線程.
}
 
int main()
{
    std::thread threads[10];
    // spawn 10 threads:
    for (int i = 0; i < 10; ++i)
        threads[i] = std::thread(do_print_id, i);
 
    std::cout << "10 threads ready to race...\n";
    go(); // go!
 
  for (auto & th:threads)
        th.join();
 
    return 0;
}
10 threads ready to race...
thread 1
thread 0
thread 2
thread 3
thread 4
thread 5
thread 6
thread 7
thread 8
thread 9

      好了,對條件變量有了一個基本的瞭解之後,我們來看看 std::condition_variable 的各個成員函數。

std::condition_variable 的拷貝構造函數被禁用,只提供了默認構造函數。

std::condition_variable::wait() 介紹:

void wait (unique_lock<mutex>& lck);

template <class predicate="">

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

   std::condition_variable提供了兩種 wait() 函數。當前線程調用 wait() 後將被阻塞(此時當前線程應該獲得了鎖(mutex),不妨設獲得鎖 lck),直到另外某個線程調用 notify_* 喚醒了當前線程。

     在線程被阻塞時,該函數會自動調用 lck.unlock() 釋放鎖,使得其他被阻塞在鎖競爭上的線程得以繼續執行。另外,一旦當前線程獲得通知(notified,通常是另外某個線程調用 notify_* 喚醒了當前線程),wait()函數也是自動調用 lck.lock(),使得lck的狀態和 wait 函數被調用時相同。

     在第二種情況下(即設置了 Predicate),只有當 pred 條件爲false 時調用 wait() 纔會阻塞當前線程,並且在收到其他線程的通知後只有當 pred 爲 true 時纔會被解除阻塞。因此第二種情況類似以下代碼:

#include <iostream>                // std::cout
#include <thread>                // std::thread, std::this_thread::yield
#include <mutex>                // std::mutex, std::unique_lock
#include <condition_variable>    // std::condition_variable
 
std::mutex mtx;
std::condition_variable cv;
 
int cargo = 0;
bool shipment_available()
{
    return cargo != 0;
}
 
// 消費者線程.
void consume(int n)
{
    for (int i = 0; i < n; ++i) {
        std::unique_lock <std::mutex> lck(mtx);
        cv.wait(lck, shipment_available);
        std::cout << cargo << '\n';
        cargo = 0;
    }
}
 
int main()
{
    std::thread consumer_thread(consume, 10); // 消費者線程.
 
    // 主線程爲生產者線程, 生產 10 個物品.
    for (int i = 0; i < 10; ++i) {
        while (shipment_available())
            std::this_thread::yield();
        std::unique_lock <std::mutex> lck(mtx);
        cargo = i + 1;
        cv.notify_one();
    }
 
    consumer_thread.join();
 
    return 0;
}
1
2
3
4
5
6
7
8
9
10

std::condition_variable::wait_for() 介紹:


 cv_status wait_for (unique_lock<mutex>& lck,const chrono::duration<rep,period>& rel_time);
 
 bool wait_for (unique_lock<mutex>& lck, const chrono::duration<rep,period>& rel_time, Predicate pred);

                     

       請看下面的例子(參考),下面的例子中,主線程等待th線程輸入一個值,然後將th線程從終端接收的值打印出來,在th線程接受到值之前,主線程一直等待,每個一秒超時一次,並打印一個 "."

#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <chrono>             // std::chrono::seconds
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable, std::cv_status
 
std::condition_variable cv;
 
int value;
 
void do_read_value()
{
    std::cin >> value;
    cv.notify_one();
}
 
int main ()
{
    std::cout << "Please, enter an integer (I'll be printing dots): \n";
    std::thread th(do_read_value);
 
    std::mutex mtx;
    std::unique_lock<std::mutex> lck(mtx);
    while (cv.wait_for(lck,std::chrono::seconds(1)) == std::cv_status::timeout) {
        std::cout << '.';
        std::cout.flush();
    }
 
    std::cout << "You entered: " << value << '\n';
 
    th.join();
    return 0;
}

         與 std::condition_variable::wait_for 類似,但是wait_until可以指定一個時間點,在當前線程收到通知或者指定的時間點 abs_time超時之前,該線程都會處於阻塞狀態。而一旦超時或者收到了其他線程的通知,wait_until返回,剩下的處理步驟和 wait_until() 類似。

       另外,wait_until的重載版本的最後一個參數 pred表示 wait_until 的預測條件,只有當 pred 條件爲 false時調用 wait()纔會阻塞當前線程,並且在收到其他線程的通知後只有當pred爲 true時纔會被解除阻塞,因此相當於如下代碼:

while (!pred())
  if ( wait_until(lck,abs_time) == cv_status::timeout)
    return pred();
return true;

std::condition_variable::notify_one() 介紹:

        喚醒某個等待(wait)線程。如果當前沒有等待線程,則該函數什麼也不做,如果同時存在多個等待線程,則喚醒某個線程是不確定的(unspecified)

請看下例(參考):

#include <iostream>                // std::cout
#include <thread>                // std::thread
#include <mutex>                // std::mutex, std::unique_lock
#include <condition_variable>    // std::condition_variable
 
std::mutex mtx;
std::condition_variable cv;
 
int cargo = 0; // shared value by producers and consumers
 
void consumer()
{
    std::unique_lock < std::mutex > lck(mtx);
    while (cargo == 0)
        cv.wait(lck);
    std::cout << cargo << '\n';
    cargo = 0;
}
 
void producer(int id)
{
    std::unique_lock < std::mutex > lck(mtx);
    cargo = id;
    cv.notify_one();
}
 
int main()
{
    std::thread consumers[10], producers[10];
 
    // spawn 10 consumers and 10 producers:
    for (int i = 0; i < 10; ++i) {
        consumers[i] = std::thread(consumer);
        producers[i] = std::thread(producer, i + 1);
    }
 
    // join them back:
    for (int i = 0; i < 10; ++i) {
        producers[i].join();
        consumers[i].join();
    }
 
    return 0;
}

std::condition_variable::notify_all() 介紹:

喚醒所有的等待(wait)線程。如果當前沒有等待線程,則該函數什麼也不做。請看下面的例子:

#include <iostream>                // std::cout
#include <thread>                // std::thread
#include <mutex>                // std::mutex, std::unique_lock
#include <condition_variable>    // std::condition_variable
 
std::mutex mtx; // 全局互斥鎖.
std::condition_variable cv; // 全局條件變量.
bool ready = false; // 全局標誌位.
 
void do_print_id(int id)
{
    std::unique_lock <std::mutex> lck(mtx);
    while (!ready) // 如果標誌位不爲 true, 則等待...
        cv.wait(lck); // 當前線程被阻塞, 當全局標誌位變爲 true 之後,
    // 線程被喚醒, 繼續往下執行打印線程編號id.
    std::cout << "thread " << id << '\n';
}
 
void go()
{
    std::unique_lock <std::mutex> lck(mtx);
    ready = true; // 設置全局標誌位爲 true.
    cv.notify_all(); // 喚醒所有線程.
}
 
int main()
{
    std::thread threads[10];
    // spawn 10 threads:
    for (int i = 0; i < 10; ++i)
        threads[i] = std::thread(do_print_id, i);
 
    std::cout << "10 threads ready to race...\n";
    go(); // go!
 
  for (auto & th:threads)
        th.join();
 
    return 0;
}

std::condition_variable_any 介紹:

與 std::condition_variable類似,只不過std::condition_variable_any的 wait 函數可以接受任何 lockable參數,而 std::condition_variable只能接受 std::unique_lock類型的參數,除此以外,和std::condition_variable幾乎完全一樣。

std::cv_status枚舉類型介紹

cv_status::no_timeout wait_for 或者wait_until沒有超時,即在規定的時間段內線程收到了通知。

cv_status::timeout  wait_for 或者 wait_until 超時。
std::notify_all_at_thread_exit

函數原型爲:

void notify_all_at_thread_exit (condition_variable& cond, unique_lock<mutex> lck);

當調用該函數的線程退出時,所有在 cond 條件變量上等待的線程都會收到通知。請看下例(參考):

#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
 
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
 
void print_id (int id) {
  std::unique_lock<std::mutex> lck(mtx);
  while (!ready) cv.wait(lck);
  // ...
  std::cout << "thread " << id << '\n';
}
 
void go() {
  std::unique_lock<std::mutex> lck(mtx);
  std::notify_all_at_thread_exit(cv,std::move(lck));
  ready = true;
}
 
int main ()
{
  std::thread threads[10];
  // spawn 10 threads:
  for (int i=0; i<10; ++i)
    threads[i] = std::thread(print_id,i);
  std::cout << "10 threads ready to race...\n";
 
  std::thread(go).detach();   // go!
 
  for (auto& th : threads) th.join();
 
  return 0;
}

             C++ 標準庫線程頭文件中的兩個條件變量類(std::condition_variablestd::condition_variable_any)、枚舉類型(std::cv_status)、以及輔助函數(std::notify_all_at_thread_exit())已經介紹完了!

參考:https://blog.csdn.net/liyazhen2011/article/details/88603161

 

 

 

 

 

 

 

 

 

 

 

發佈了520 篇原創文章 · 獲贊 1266 · 訪問量 186萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章