頭文件主要包含了與條件變量相關的類和函數。相關的類包括 std::condition_variable
和 std::condition_variable_any
,還有枚舉類型std::cv_status
。另外還包括函數 std::notify_all_at_thread_exit()
,下面分別介紹一下以上幾種類型。
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 // std::cout
#include // std::thread
#include // std::mutex, std::unique_lock
#include // std::condition_variable
std::mutex mtx; // 全局互斥鎖.
std::condition_variable cv; // 全局條件變量.
bool ready = false; // 全局標志位.
void do_print_id(int id)
{
std::unique_lock lck(mtx);
while (!ready) // 如果標志位不為 true, 則等待...
cv.wait(lck); // 當前線程被阻塞, 當全局標志位變為 true 之後,
// 線程被喚醒, 繼續往下執行打印線程編號id.
std::cout << "thread " << id << '\n';
}
void go()
{
std::unique_lock 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& lck);
template
void wait (unique_lock& 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
時才會被解除阻塞。因此第二種情況類似以下代碼:
while (!pred()) wait(lck);
#include // std::cout
#include // std::thread, std::this_thread::yield
#include // std::mutex, std::unique_lock
#include // 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 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 lck(mtx);
cargo = i + 1;
cv.notify_one();
}
consumer_thread.join();
return 0;
}
1
2
3
4
5
6
7
8
9
10
template
cv_status wait_for (unique_lock& lck,
const chrono::duration& rel_time);
template
bool wait_for (unique_lock& lck,
const chrono::duration& rel_time, Predicate pred);
與std::condition_variable::wait()
類似,不過 wait_for
可以指定一個時間段,在當前線程收到通知或者指定的時間 rel_time
超時之前,該線程都會處於阻塞狀態。而一旦超時或者收到了其他線程的通知,wait_for
返回,剩下的處理步驟和 wait()
類似。
另外,wait_for
的重載版本的最後一個參數pred
表示 wait_for
的預測條件,只有當 pred
條件為false
時調用 wait()
才會阻塞當前線程,並且在收到其他線程的通知後只有當 pred
為 true
時才會被解除阻塞,因此相當於如下代碼:
return wait_until (lck, chrono::steady_clock::now() + rel_time, std::move(pred));
請看下面的例子(參考),下面的例子中,主線程等待th
線程輸入一個值,然後將th
線程從終端接收的值打印出來,在th
線程接受到值之前,主線程一直等待,每個一秒超時一次,並打印一個 "."
:
#include // std::cout
#include // std::thread
#include // std::chrono::seconds
#include // std::mutex, std::unique_lock
#include // 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 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;
}
template
cv_status wait_until (unique_lock& lck,
const chrono::time_point& abs_time);
template
bool wait_until (unique_lock& lck,
const chrono::time_point& abs_time,
Predicate pred);
與 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;
喚醒某個等待(wait
)線程。如果當前沒有等待線程,則該函數什麼也不做,如果同時存在多個等待線程,則喚醒某個線程是不確定的(unspecified)
。
請看下例(參考):
#include // std::cout
#include // std::thread
#include // std::mutex, std::unique_lock
#include // 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;
}
喚醒所有的等待(wait)
線程。如果當前沒有等待線程,則該函數什麼也不做。請看下面的例子:
#include // std::cout
#include // std::thread
#include // std::mutex, std::unique_lock
#include // std::condition_variable
std::mutex mtx; // 全局互斥鎖.
std::condition_variable cv; // 全局條件變量.
bool ready = false; // 全局標志位.
void do_print_id(int id)
{
std::unique_lock lck(mtx);
while (!ready) // 如果標志位不為 true, 則等待...
cv.wait(lck); // 當前線程被阻塞, 當全局標志位變為 true 之後,
// 線程被喚醒, 繼續往下執行打印線程編號id.
std::cout << "thread " << id << '\n';
}
void go()
{
std::unique_lock 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
類似,只不過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 lck);
當調用該函數的線程退出時,所有在 cond
條件變量上等待的線程都會收到通知。請看下例(參考):
#include // std::cout
#include // std::thread
#include // std::mutex, std::unique_lock
#include // std::condition_variable
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void print_id (int id) {
std::unique_lock lck(mtx);
while (!ready) cv.wait(lck);
// ...
std::cout << "thread " << id << '\n';
}
void go() {
std::unique_lock 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;
}
好了,到此為止,
頭文件中的兩個條件變量類(std::condition_variable
和std::condition_variable_any
)、枚舉類型(std::cv_status
)、以及輔助函數(std::notify_all_at_thread_exit()
)都已經介紹完了。