c++11 多線程(4) condition_varible 總結


接著上節 atomic,本節主要介紹condition_varible的內容,練習代碼地址。本文參考http://www.cplusplus.com/reference/condition_variable/http://en.cppreference.com/w/cpp/header/condition_variable學習。

一、 <condition_variable>頭文件結構

<condition_variable>頭文件主要包含了與條件變量相關的類和函數。主要含有類condition_variable和condition_variable_any,枚舉cv_status,函數notify_all_at_thread_exit。

Classes description
condition_variable 提供了std::unique_lock 相關聯的條件變量
condition_variable_any 提供與任何鎖類型相關聯的條件變量
Enum classes description
cv_status 列出在條件變量上限時等待的可能結果(枚舉)
Functions description
notify_all_at_thread_exit 當這個線程完全完成(函數)時,調度調用notify_all來調用

結構定義類似:

namespace std {
 
    class condition_variable;
    class condition_variable_any;
 
    void notify_all_at_thread_exit(condition_variable& cond, unique_lock<mutex> lk);
 
    enum class cv_status {
        no_timeout,
        timeout
    };
}

二、Classes

1. class condition_variable;

  • 條件變量類是一個同步原語,它可以用來阻塞一個線程或多個線程,直到另一個線程同時修改一個共享變量(條件),并通知條件變量。
  • 條件變量是能夠阻塞調用線程的對象,直到通知恢復為止。
    它使用unique_lock(在互斥鎖上)來鎖定線程,當它的一個等待函數被調用時。線程仍然被阻塞,直到被另一個線程喚醒,該線程調用同一個條件變量對象上的通知函數。
  • 類型條件變量的對象總是使用unique_lock < mutex >等待:對于可以使用任何類型的可鎖定類型的選項,參見condition_variable_any。
    示例1
// condition_variable example
#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);
  ready = true;
  cv.notify_all();
}

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";
  go();                       // go!

  for (auto& th : threads) th.join();

  return 0;
}

示例2

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
 
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
 
void worker_thread()
{
    // Wait until main() sends data
    std::unique_lock<std::mutex> lk(m);
    cv.wait(lk, []{return ready;});
 
    // after the wait, we own the lock.
    std::cout << "Worker thread is processing data\n";
    data += " after processing";
 
    // Send data back to main()
    processed = true;
    std::cout << "Worker thread signals data processing completed\n";
 
    // Manual unlocking is done before notifying, to avoid waking up
    // the waiting thread only to block again (see notify_one for details)
    lk.unlock();
    cv.notify_one();
}
 
int main()
{
    std::thread worker(worker_thread);
 
    data = "Example data";
    // send data to the worker thread
    {
        std::lock_guard<std::mutex> lk(m);
        ready = true;
        std::cout << "main() signals data ready for processing\n";
    }
    cv.notify_one();
 
    // wait for the worker
    {
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{return processed;});
    }
    std::cout << "Back in main(), data = " << data << '\n';
 
    worker.join();
    return 0;
}

1. Member functions

  1. (constructor)構造函數
    定義:
       condition_variable();                                      default (1)
       condition_variable (const condition_variable&) = delete;   copy [deleted] (2)
       operator=   [deleted]

* 構造一個condition_variable對象。
* 條件變量不能被復制/移動(這個類型的拷貝構造函數和賦值操作符都被刪除)。
* 如果構建失敗,將拋出system_error異常, 根據庫的實現,它可能會在其他情況下拋出異常。

exception type error condition description
system_error errc::resource_unavailable_try_again 資源限制(除了內存分配)防止初始化
  1. (destructor)析構函數
    * 破壞condition_variable對象。
    * 在此條件下被阻塞的任何線程在調用此析構函數之前都將被通知。在這個析構函數被調用之后,沒有線程將開始等待。
    注意
    1. 如果所有線程都被通知,調用析構函數是安全的。它們不需要退出各自的等待函數:一些線程可能還在等待重新獲得相關的鎖,或者等待調度在重新獲得它之后運行。
    2.程序員必須確保在啟動析構函數時,沒有線程試圖等待*this,特別是當等待線程調用循環中的等待函數或使用執行謂詞的等待函數的重載時。

2. Wait functions

  1. std::condition_variable::wait
    void wait (unique_lock<mutex>& lck); unconditional (1)
    template <class Predicate> predicate (2)
    void wait (unique_lock<mutex>& lck, Predicate pred);
    1、 當前線程的執行(該線程將鎖定lck的互斥鎖)將被阻塞,直到被通知。
    2、 在阻塞線程的時刻,該函數將自動調用lck.unlock(),允許其他鎖定的線程繼續運行。
    3、 一旦通知(顯式地,通過其他線程),函數就unblock并調用lck. lock(),在調用函數時將lck留在相同的狀態。然后函數返回(注意,最后一個互斥鎖可能會在返回之前阻塞線程)。
    4、一般情況下,函數會被另一個線程的調用喚醒,無論是對成員notify_one還是成員notify_all。但是某些實現可能產生虛假的喚醒調用,而不需要調用這些函數。因此,該函數的用戶將確保滿足恢復的條件。
    5、 定義(2)如果指定pred,如果pred返回false,則函數只會阻塞,并且只有當它變為true時,通知才能解除線程(這對于檢查偽喚醒調用特別有用)。
    類似這樣的實現: while (!pred()) wait(lck);
    示例3
  // condition_variable::wait (with predicate)
  #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);
      // consume:
      std::cout << cargo << '\n';
      cargo=0;
    }
  }

  int main ()
  {
    std::thread consumer_thread (consume,10);

    // produce 10 items when needed:
    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;
  }

示例4

  #include <iostream>
  #include <condition_variable>
  #include <thread>
  #include <chrono>
 
  std::condition_variable cv;
  std::mutex cv_m; // This mutex is used for three purposes:
                 // 1) to synchronize accesses to i
                 // 2) to synchronize accesses to std::cerr
                 // 3) for the condition variable cv
  int i = 0;
 
  void waits()
  {
      std::unique_lock<std::mutex> lk(cv_m);
      std::cerr << "Waiting... \n";
      cv.wait(lk, []{return i == 1;});
      std::cerr << "...finished waiting. i == 1\n";
  }
 
  void signals()
  {
      std::this_thread::sleep_for(std::chrono::seconds(1));
      {
          std::lock_guard<std::mutex> lk(cv_m);
          std::cerr << "Notifying...\n";
      }
      cv.notify_all();
 
      std::this_thread::sleep_for(std::chrono::seconds(1));
      {
          std::lock_guard<std::mutex> lk(cv_m);
          i = 1;
          std::cerr << "Notifying again...\n";
      }
      cv.notify_all();
  }
 
  int main()
  {
      std::thread t1(waits), t2(waits), t3(waits), t4(signals);
      t1.join(); 
      t2.join(); 
      t3.join();
     t4.join();
      return 0;
  }
  1. std::condition_variable::wait_for
        (1) unconditional   
        template <class Rep, class Period>
            cv_status wait_for (unique_lock<mutex>& lck,
                      const chrono::duration<Rep,Period>& rel_time);
        (2) predicate   
        template <class Rep, class Period, class Predicate>
           bool wait_for (unique_lock<mutex>& lck,
                      const chrono::duration<Rep,Period>& rel_time, Predicate pred);

1、當前執行線程在rel_time時間內或者在被通知之前(該線程將鎖定lck的互斥鎖)被阻塞(如果后者先發生的話)。
2、在阻塞線程的時刻,該函數將自動調用lck.解鎖(),允許其他鎖定的線程繼續運行。
3、一旦通知或一次rel_time時間段已經過了,函數將會unblocks并調用lck. lock(),將lck與調用函數時的狀態保持一致。然后函數返回(注意,最后一個互斥鎖可能會在返回之前阻塞線程)。
4、一般情況下,函數會被另一個線程的調用喚醒,無論是對成員notify_one還是成員notify_all。但是某些實現可能產生虛假的喚醒調用,而不需要調用這些函數。因此,該函數的用戶將確保滿足恢復的條件。
5、如果指定pred(定義2),如果pred返回false,則函數只會阻塞,并且只有當它變為true時,通知才能解除線程(這對于檢查偽喚醒調用特別有用)。它的行為好像是:
return wait_until (lck, chrono::steady_clock::now() + rel_time, std::move(pred));
示例5

  // condition_variable::wait_for example
  #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 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 (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::endl;
    }
    std::cout << "You entered: " << value << '\n';

    th.join();

    return 0;
  }

示例6

  #include <iostream>
  #include <atomic>
  #include <condition_variable>
  #include <thread>
  #include <chrono>
  //using namespace std::chrono_literals;
 
  std::condition_variable cv;
  std::mutex cv_m;
  int i;
 
  void waits(int idx)
  {
      std::unique_lock<std::mutex> lk(cv_m);
      if(cv.wait_for(lk, idx*std::chrono::milliseconds(100), []{return i == 1;})) 
          std::cerr << "Thread " << idx << " finished waiting. i == " << i << '\n';
      else
          std::cerr << "Thread " << idx << " timed out. i == " << i << '\n';
  }
 
  void signals()
  {
      std::this_thread::sleep_for(std::chrono::milliseconds(120));
      std::cerr << "Notifying...\n";
      cv.notify_all();
      std::this_thread::sleep_for(std::chrono::milliseconds(100));
      {
          std::lock_guard<std::mutex> lk(cv_m);
          i = 1;
      }
      std::cerr << "Notifying again...\n";
      cv.notify_all();
  }
 
  int main()
  {
      std::thread t1(waits, 1), t2(waits, 2), t3(waits, 3), t4(signals);
      t1.join(); t2.join(), t3.join(), t4.join();
      return 0;
  }
  1. std::condition_variable::wait_until
        (1) unconditional   
        template <class Clock, class Duration>
            cv_status wait_until (unique_lock<mutex>& lck,
                        const chrono::time_point<Clock,Duration>& abs_time);
        (2) predicate   
        template <class Clock, class Duration, class Predicate>
             bool wait_until (unique_lock<mutex>& lck,
                        const chrono::time_point<Clock,Duration>& abs_time,
                        Predicate pred);

1、當前線程的執行(該線程將鎖定lck的互斥鎖)會被阻塞,直到被通知或到達abs_time時間點,無論那一個第一次發生。
2、在線程還處在阻塞時,該函數將自動調用lck.unlock(),允許其他鎖定的線程繼續運行。
3、一旦被通知或一旦abs_time時間到了,函數就會接觸阻塞狀態并調用lck. lock()。在調用函數時將lck與函數的狀態保持在同一狀態。然后函數返回(注意,最后一個互斥鎖可能會在返回之前阻塞線程)。
4、一般情況下,函數會被另一個線程的調用喚醒,無論是對成員notify_one還是成員notify_all。但是某些實現可能產生虛假的喚醒調用,而不需要調用這些函數。因此,該函數的使用者將要確保滿足恢復的條件。
5、(定義2)如果指定pred,如果pred返回false,則函數只會阻塞,并且只有當它變為true時,通知才能解除線程(這對于檢查偽喚醒調用特別有用)。它的行為好像是:

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

示例7

  #include <iostream>
  #include <atomic>
  #include <condition_variable>
  #include <thread>
  #include <chrono>
  //using namespace std::chrono_literals;
 
  std::condition_variable cv;
  std::mutex cv_m;
  std::atomic<int> i{0};
 
  void waits(int idx)
  {
      std::unique_lock<std::mutex> lk(cv_m);
      auto now = std::chrono::system_clock::now();
      if(cv.wait_until(lk, now + idx*std::chrono::milliseconds(100), [](){return i == 1;}))
          std::cerr << "Thread " << idx << " finished waiting. i == " << i << '\n';
      else
          std::cerr << "Thread " << idx << " timed out. i == " << i << '\n';
  }
 
  void signals()
  {
      std::this_thread::sleep_for(std::chrono::milliseconds(120));
      std::cerr << "Notifying...\n";
      cv.notify_all();
      std::this_thread::sleep_for(std::chrono::milliseconds(100));
      i = 1;
      std::cerr << "Notifying again...\n";
      cv.notify_all();
  }
 
  int main()
  {
      std::thread t1(waits, 1), t2(waits, 2), t3(waits, 3), t4(signals);
      t1.join(); 
      t2.join();
      t3.join();
      t4.join();
      return 0;
  }

3. Notify functions

  1. std::condition_variable::notify_one
    void notify_one() noexcept; //定義
    1、打開當前等待該條件的一個線程。如果沒有線程在等待,則該函數什么都不做。如果多于一個,則不指定哪個線程被選中。
    2、該指令針對這個單獨的條件變量。這使得notify_one()不可能被延遲,并且在調用notify_one()之后才開始等待。
    3、通知線程不需要將鎖保存在與等待線程所持有的相同互斥鎖上;事實上,這樣做是一個悲觀的方法,因為通知的線程會立即阻塞,等待通知線程釋放鎖。然而,一些實現(特別是許多pthreads實現)識別這種情況,并避免這種“匆忙而等待”的場景,通過將等待的線程從條件變量的隊列直接傳遞到通知調用的互斥鎖的隊列中,而不將其喚醒。
    示例8
  // condition_variable::notify_one
  #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 produce,consume;

  int cargo = 0;     // shared value by producers and consumers

  void consumer () {
    std::unique_lock<std::mutex> lck(mtx);
    while (cargo==0) consume.wait(lck);
    std::cout << cargo << '\n';
    cargo=0;
    produce.notify_one();
  }

  void producer (int id) {
    std::unique_lock<std::mutex> lck(mtx);
    while (cargo!=0) produce.wait(lck);
    cargo = id;
    consume.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;
 }

示例9

  #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();
      return 0;
  }
  1. std::condition_variable::notify_all
    void notify_all() noexcept; // 定義
    解鎖當前等待該條件的所有線程。如果沒有線程在等待,則該函數什么都不做。
    注意:
    1、notify_one()/notify_all()和wait()/wait_for()/wait_until() 三個原子部分中的每一個影響都在一個完全的順序(解鎖+等待, 喚醒, 鎖)中,可以被視為一個原子變量的修改順序:該命令是特定于這個單獨的條件變量的。這使得notify_one()不可能被延遲,并且在調用notify_one()之后才開始等待。
    2、通知線程不需要將鎖保存在與等待線程所持有的相同互斥鎖上;事實上,這樣做是一個悲觀的方法,因為通知的線程會立即阻塞,等待通知線程釋放鎖。

    示例10
  // condition_variable::notify_all
  #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);
    ready = true;
    cv.notify_all();
  }

  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";
    go();                       // go!

    for (auto& th : threads) th.join();

    return 0;
  }

示例11

  #include <iostream>
  #include <condition_variable>
  #include <thread>
  #include <chrono>
 
  std::condition_variable cv;
  std::mutex cv_m; // This mutex is used for three purposes:
                   // 1) to synchronize accesses to i
                   // 2) to synchronize accesses to std::cerr
                   // 3) for the condition variable cv
  int i = 0;
 
  void waits()
  {
      std::unique_lock<std::mutex> lk(cv_m);
      std::cerr << "Waiting... \n";
      cv.wait(lk, []{return i == 1;});
      std::cerr << "...finished waiting. i == 1\n";
  }
   
  void signals()
  {
      std::this_thread::sleep_for(std::chrono::seconds(1));
      {
          std::lock_guard<std::mutex> lk(cv_m);
          std::cerr << "Notifying...\n";
      }
      cv.notify_all();
 
      std::this_thread::sleep_for(std::chrono::seconds(1));
 
      {
          std::lock_guard<std::mutex> lk(cv_m);
          i = 1;
          std::cerr << "Notifying again...\n";
      }
      cv.notify_all();
  }
 
  int main()
  {
      std::thread t1(waits), t2(waits), t3(waits), t4(signals);
      t1.join(); 
      t2.join(); 
      t3.join();
      t4.join();
      return 0;
  }

2. class condition_variable_any;

1、與條件變量一樣,除了它的等待函數可以將任何可鎖類型作為參數(條件變量對象只能使用unique_lock < mutex >)。除此之外,它們是相同的。

2、std::condition_variable_any是std::condition_variable的泛化。而std::condition_variable 只能用于std::unique_lock < std::mutex >,condition_variable_any可以操作任何滿足基本要求的鎖。參見std::condition_變量,用于描述條件變量的語義。類std::condition_variable_any是一個標準布局類。它不可拷貝構造的,移動可構造的,不可拷貝復制和移動復制。

3、如果鎖是std::unique_lock,std::condition_variable可以提供更好的性能。
注意:
std::condition_variable_any與自定義的可鎖類型是用來提供方便的可中斷等待:自定義鎖操作會像預期的那樣鎖定相關的互斥對象,并且在接收到中斷信號時也執行必要的設置來通知這個條件變量。

1. Member functions

  1. (constructor)構造函數
    default (1) condition_variable_any();
    copy [deleted] (2) condition_variable_any (const condition_variable_any&) = delete;
    構造std::condition_variable_any類型的對象. 不能通過拷貝和移動構造(這個類型的拷貝構造函數和賦值操作符都被刪除)。

注意:
如果構造失敗,會拋出一個系統異常:

根據庫的實現,它可能會在其他情況下拋出異常如bad_alloc無法分配內存。
operator= [deleted] 不允許復制。

  1. (destructor)析構函數
    析構condition_variable_any對象。在此條件下被阻塞的任何線程在調用此析構函數之前都將被通知。在這個析構函數被調用之后,沒有線程將開始等待。
    注意:
    1、 如果所有線程都被通知,調用析構函數是安全的。它們不需要退出各自的等待函數:一些線程可能還在等待重新獲得相關的鎖,或者等待調度在重新獲得它之后運行。
    2、 程序員必須確保在啟動析構函數時,沒有線程試圖等待,特別是當等待線程調用循環中的等待函數或使用執行謂詞的等待函數的重載時。

2. Wait functions

  1. [std::condition_variable_any::wait]
    (http://www.cplusplus.com/reference/condition_variable/condition_variable_any/wait/)
        unconditional (1)   template <class Lock> void wait (Lock& lck);
        predicate (2)       template <class Lock, class Predicate>
                                  void wait (Lock& lck, Predicate pred);

1、當前線程(當前正在鎖定lck)的執行將被阻止,直到通知為止。
2、在阻止線程的時刻,該函數自動調用lck.unlock(),允許其他鎖定線程繼續。
3、一旦通知(顯式地,通過某個其他線程),函數解除阻塞和調用lck.lock(),使lck處于與調用函數時相同的狀態。然后函數返回(注意,這個最后的互斥鎖定可能在返回之前再次阻止線程)。
4、通常,該函數被通知通過另一個線程的呼叫喚醒成員notify_one 或 notify_all。但是某些實現可能會產生虛假的喚醒呼叫,而不會調用這些功能。因此,該功能的用戶應確保其恢復條件得到滿足。
5、定義(2): 如果指定pred,如果pred返回false,則函數只會阻塞,并且只有當它變為true時,通知才能解除線程(這對于檢查偽喚醒調用特別有用)。表現為:

         while  (! pred ()) { 
              wait ( lock ); 
         }

示例12

  // condition_variable_any::wait (with predicate)
  #include <iostream>           // std::cout
  #include <thread>             // std::thread, std::this_thread::yield
  #include <mutex>              // std::mutex
  #include <condition_variable> // std::condition_variable_any

  std::mutex mtx;
  std::condition_variable_any cv;

  int cargo = 0;
  bool shipment_available() {return cargo!=0;}

  void consume (int n) {
    for (int i=0; i<n; ++i) {
      mtx.lock();
      cv.wait(mtx,shipment_available);
      // consume:
      std::cout << cargo << '\n';
      cargo=0;
      mtx.unlock();
    }
  }

  int main ()
  {
    std::thread consumer_thread (consume,10);

    // produce 10 items when needed:
    for (int i=0; i<10; ++i) {
      while (shipment_available()) std::this_thread::yield();
      mtx.lock();
      cargo = i+1;
      cv.notify_one();
      mtx.unlock();
    }

    consumer_thread.join();

    return 0;
  }

2.std::condition_variable_any::wait_for

       unconditional (1)    
            template <class Lock, class Rep, class Period>
                cv_status wait_for (Lock& lck,
                           const chrono::duration<Rep,Period>& rel_time);
       predicate (2)    
            template <class Lock, class Rep, class Period, class Predicate>
                 bool wait_for (Lock& lck,
                      const chrono::duration<Rep,Period>& rel_time, Predicate pred);

1、當前線程(當前鎖定lck)的執行在rel_time期間被阻塞,或者直到通知(如果后者首先發生)。
2、在阻止線程的時刻,該函數自動調用lck.unlock(),允許其他鎖定線程繼續。
3、一旦通知或一旦rel_time已經過去,該函數解除阻塞和調用lck.lock(),使lck處于與調用函數時相同的狀態。然后函數返回(注意,這個最后的互斥鎖定可能在返回之前再次阻止線程)。
4、通常,該函數被通知通過另一個線程的呼叫喚醒成員notify_one 或 notify_one。但是某些實現可能會產生虛假的喚醒呼叫,而不會調用這些功能。因此,該功能的用戶應確保其恢復條件得到滿足。
5、版本(2):如果指定pred,如果pred返回false,則函數只會阻塞,并且只有當它變為true時,通知才能解除線程(這對于檢查偽喚醒調用特別有用)。可以理解為

       return wait_until (lck, chrono::steady_clock::now() + rel_time, std::move(pred));

示例13

  // condition_variable_any::wait_for example
  #include <iostream>           // std::cout
  #include <thread>             // std::thread
  #include <chrono>             // std::chrono::seconds
  #include <mutex>              // std::mutex
  #include <condition_variable> // std::condition_variable_any,       std::cv_status

  std::condition_variable_any cv;

  int value;

  void read_value() {
    std::cin >> value;
    cv.notify_one();
  }

  int main ()
  {
    std::cout << "Please, enter an integer (I'll be printing dots): ";
    std::thread th (read_value);

    std::mutex mtx;
    mtx.lock();
    while   (cv.wait_for(mtx,std::chrono::seconds(1))==std::cv_status::timeout) {
      std::cout << '.';
    }
    std::cout << "You entered: " << value << '\n';
    mtx.unlock();

    th.join();

    return 0;
  }
  1. std::condition_variable_any::wait_until
       unconditional (1)    
       template <class Lock, class Clock, class Duration>
          cv_status wait_until (Lock& lck,
                                const chrono::time_point<Clock,Duration>& abs_time);
       predicate (2)    
       template <class Lock, class Clock, class Duration, class Predicate>
          bool wait_until (Lock& lck,
                           const chrono::time_point<Clock,Duration>& abs_time, 
                           Predicate pred);

1、當前線程的執行(當前鎖定lck)將被阻塞,直到通知或直到abs_time,以先發生者為準。
2、在阻止線程的時刻,該函數自動調用lck.unlock(),允許其他鎖定線程繼續。
3、一旦通知或一旦為abs_time,函數解除阻塞和調用lck.lock(),使lck處于與調用函數時相同的狀態。然后函數返回(注意,這個最后的互斥鎖定可能在返回之前再次阻止線程)。
4、通常,該函數被通知通過另一個線程的呼叫喚醒成員notify_one 或會員 notify_one。但是某些實現可能會產生虛假的喚醒呼叫,而不會調用這些功能。因此,該功能的用戶應確保其恢復條件得到滿足。
5、版本2:如果指定pred,如果pred返回false,則函數只會阻塞,并且只有當它變為true時,通知才能解除線程(這對于檢查偽喚醒調用特別有用)。表現為:

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

示例14

#include <iostream>
#include <atomic>
#include <condition_variable>
#include <thread>
#include <chrono>
//using namespace std::chrono_literals;
 
std::condition_variable cv;
std::mutex cv_m;
std::atomic<int> i{0};
 
void waits(int idx)  {
    std::unique_lock<std::mutex> lk(cv_m);
    auto now = std::chrono::system_clock::now();
    if(cv.wait_until(lk, now + idx*std::chrono::milliseconds(100), [](){return i == 1;}))
         std::cerr << "Thread " << idx << " finished waiting. i == " << i << '\n';
     else
         std::cerr << "Thread " << idx << " timed out. i == " << i << '\n';
}
 
void signals() {
    std::this_thread::sleep_for(std::chrono::milliseconds(120));
    std::cerr << "Notifying...\n";
    cv.notify_all();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    i = 1;
    std::cerr << "Notifying again...\n";
    cv.notify_all();
}
 
int main() {
    std::thread t1(waits, 1), t2(waits, 2), t3(waits, 3), t4(signals);
    t1.join(); 
    t2.join();
    t3.join();
    t4.join();
    return 0;
}

3. Notify functions

  1. std::condition_variable_any::notify_one
    同上面std::condition_variable的函數。
    unblock當前等待該條件的一個線程。如果沒有線程在等待,則該函數什么都不做。如果多于一個,則不指定哪個線程被選中。
    示例15
  // condition_variable_any::notify_one
  #include <iostream>           // std::cout
  #include <thread>             // std::thread
  #include <mutex>              // std::mutex
  #include <condition_variable> // std::condition_variable_any

  std::mutex mtx;
  std::condition_variable_any cv;

  int cargo = 0;     // shared value by producers and consumers

 void consumer () {
      mtx.lock();
      while (cargo==0) cv.wait(mtx);
      std::cout << cargo << '\n';
      cargo=0;
      mtx.unlock();
  }

  void producer (int id) {
      mtx.lock();
      cargo = id;
      cv.notify_one();
      mtx.unlock();
  }
  
  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;
  }

本例子在vs2013運行結果 1-10,在ubuntu14.04,會出現死鎖等待情況。

  1. std::condition_variable_any::notify_all
    同上面std::condition_variable的函數。打開當前等待該條件的所有線程。如果沒有線程在等待,則該函數什么都不做。
    示例16
  // condition_variable_any::notify_all
  #include <iostream>           // std::cout
  #include <thread>             // std::thread
  #include <mutex>              // std::mutex
  #include <condition_variable> // std::condition_variable_any

  std::mutex mtx;
  std::condition_variable_any cv;
  bool ready = false;

  void print_id (int id) {
    mtx.lock();
    while (!ready) cv.wait(mtx);
    // ...
    std::cout << "thread " << id << '\n';
    mtx.unlock();
  }

  void go() {
    mtx.lock();
    ready = true;
    cv.notify_all();
    mtx.unlock();
  }

  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";
    go();                       // go!

    for (auto& th : threads) th.join();

    return 0;
  }

三、 Enum classes

1. std::cv_status

    enum class cv_status;   定義

條件變量狀態:
表示是否由于超時而返回的函數。
這種類型的值由condition_variable和condition_variable_any的成員wait_for和wait_until返回。
定義類似:

 enum class cv_status { no_timeout, timeout };
cv_status::no_timeout :  該函數返回沒有超時
cv_status::timeout : 該函數由于達到其時間限制(timeout)而返回。

四、Functions

1. std::notify_all_at_thread_exit

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

當調用線程退出時,等待在cond上的所有線程都被通知恢復執行。
該函數還獲得由lck管理的mutex對象上的鎖的所有權,該對象在內部由函數存儲,并在線程退出時解鎖(僅在通知所有線程之前),行為如下:一旦所有具有線程存儲時間的對象都被銷毀:1 lck.unlock(); 2 cond.notify_all();
注意
如果lock. mutex()沒有被當前線程鎖定,則調用此函數是未定義的行為。
如果lock. mutex()與當前在同一條件變量中等待的所有其他線程使用的互斥對象不相同,則調用此函數。
所提供的鎖一直保持到線程退出。一旦調用這個函數,就不會有更多的線程可能獲得相同的鎖以等待cond。如果某個線程在這個條件變量上等待,它不應該嘗試釋放并重新獲取鎖,當它被錯誤地喚醒時。
在典型的用例中,這個函數是由一個獨立的線程調用的最后一個函數。

示例17

  // notify_all_at_thread_exit
  #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;
  }

上面的例子ubuntu14.04 g++4.8.4 編譯時報錯notify_all_at_thread_exit 不是 std的成語函數; 原因:/usr/include/c++/4.8/condition_variable文件中沒有對notify_all_at_thread_exit函數進行定義; vs2013中VC的include下的 condition_variable有定義,所以使用vs2013編譯運行正常。

到這里condition_varible 頭文件中的內容已經列完。 下一篇 c++11 多線程(5) future 總結

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容

  • 接著上上節 thread ,本節主要介紹mutex的內容,練習代碼地址。<mutex>:該頭文件主要聲明了與互斥量...
    jorion閱讀 12,536評論 2 4
  • <condition_variable > 頭文件主要包含了與條件變量相關的類和函數。相關的類包括 std::co...
    張霸天閱讀 3,786評論 1 0
  • 本文主要是針對C++中多線程并發操作參見(cplusplus)進行解釋,文章從下面幾個方面進行學習,分別介紹多線程...
    jorion閱讀 9,866評論 0 10
  • 接著上節 mutex,本節主要介紹atomic的內容,練習代碼地址。本文參考http://www.cplusplu...
    jorion閱讀 73,757評論 1 14
  • 本文根據眾多互聯網博客內容整理后形成,引用內容的版權歸原始作者所有,僅限于學習研究使用,不得用于任何商業用途。 互...
    深紅的眼眸閱讀 1,113評論 0 0