當一個線程等待另一個線程完成任務時,有幾種選擇。
第一種,線程持續性的檢查共享數據標誌。但是這樣會造成性能的浪費,而且重複性的訪問勢必會對數據進行長時間的上鎖,從而造成其他線程無法訪問,這樣線程就會持續等待。
第二種,比第一種稍微好點,在線程等待時間內使用std::this_thread::sleep_for
進行週期性的間歇。但是在某些環境下會造成一些遺漏,例如一些高節奏遊戲中很可能會造成丟幀,或者在一個實時應用中超越一個時間片。
第三種,使用c++多線程標準庫中的“條件變量”去實現,即一個線程完成事會觸發另外一個線程中的變量覺醒從而喚醒整個線程。
std::condition_variale
和std::condition_variable_any
都包含於<condition_variable>
頭文件的聲明中。std::condition_variale
需要搭配std::mutex使用,系統資源開銷較小,對硬件要求低,作爲首選。std::condition_variable_any
可以搭配任何互斥量,使用比較靈活,但是系統資源的開銷大,對硬件要求較高,作爲第二選擇。
例1 使用std::condition_variale
處理數據等待
std::mutex mut;
std::queue<data_chunk> data_queue; //1
std::condition_variable data_cond;
void data_prparation_thread()
{
while(more_data_to_prepare())
{
data_chunk const data = preapare_data();
std::lock_guard<std::mutex> lk(mut);
data_queue.push(data); //2
data_cond.notify_one(); //3
}
}
void data_processing_thread()
{
while(true)
{
std::unique_lock<std::mutex> lk(mut); //4
data_cond.wait(lk, []{return !data_queue.empty();}); //5
data_chunk data = data_queue.front();
data_queue.pop();
lk.unlock(); //6
process(data);
if(is_last_chunk(data))
break;
}
}
首先,聲明一個用來在線程中傳遞數據的隊列(如步驟1所示)。data_prparation_thread()
函數中,當數據準備好的時候,對互斥量上鎖用來鎖定隊列,並將準備好的數據推入隊列中(如步驟2所示)。然後調用std::condition_variable::notify_one()
函數,對等待線程進行通知(如步驟3所示)。
另外,在另一個線程中,data_processing_thread()
函數首先對隊列進行上鎖,然後調用
std::condition_variable::wait()
函數判斷隊列是否爲空。如果隊列爲空,則解鎖互斥量,釋放隊列,並使線程進入阻塞狀態或睡眠狀態等待其他線程中的std::condition_variable::notify_one()
函數的喚醒。當該線程被喚醒時,將再次獲取互斥鎖並判斷判斷條件,若滿足條件就從std::condition_variable::wait()
函數中返回,進入後續程序。
例2. 構建線程安全隊列
#include <queue>
#include <memory>
#include <mutex>
#include <condition_variable>
template<typename T>
class threadsafe_queue
{
private:
mutable std::mutex mut;
std::queue<T> data_queue;
std::condition_variable data_cond;
public:
threadsafe_queue()
{}
threadsafe_queue(threadsafe_queue const& other)//複製構造函數
{
std::lock_guard<std::mutex> lk(other.mut);
data_queue = other.data_queue;
}
void push(T new_value)
{
std::lock_guard<std::mutex> lk(mut);
data_queue.push(new_value);
data_cond.notify_one();
}
void wait_and_pop(T& value)
{
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(lk, [this]{return !data_queue.empty();}) //等待push函數中data_cond.notify_one()函數的喚醒
value = data_queue.front();
data_queue.pop();
}
std::shared_ptr<T> wait_and_pop()
{
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(lk, [this]{return !data_queue.empty();});//等待喚醒
std::shared_ptr<T> res(std::make_shared<T> (data_queue.front()));
data_queue.pop();
return res;
}
bool try_pop(T& value)
{
std::lock_guard<std::mutex> lk(mut);
if(data_queue.empty())
return false;
value = data_queue.front();
data_queue.pop();
return true;
}
std::shared_ptr<T> try_pop()
{
std::lock_guard<std::mutex> lk(mut);
if(data_queue.empty())
return std::shared_ptr<T>();
std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
data_queue.pop();
return res;
}
bool empty() const
{
std::lock_guard<std::mutex> lk(mut);
return data_queue.empty();
}
};
在例2中有wait_and_pop()
和try_pop()
兩種彈出函數,try_pop()
函數僅僅可以進行線程安全的彈出操作但是功能比較弱,無法進行等待。而wait_and_pop()
則可以進行判斷和等待。