c++線程池實現(三)同步隊列實現

前面設計同步隊列的代碼,下面詳細說說實現。

Push

void Push(T&& task) {
    std::unique_lock<std::mutex> lock{mutex_};
    not_full_variable_.wait(lock, [this]{ return queue_.size() < max_size_; });
    queue_.push(std::forward<T>(task));
    not_empty_variable_.notify_one();
}

void Push(const T& task) {
    std::unique_lock<std::mutex> lock{mutex_};
    not_full_variable_.wait(lock, [this]{ return queue_.size() < max_size_; });
    queue_.push(std::move(task));
    not_empty_variable_.notify_one();
}

爲了保證線程安全,首先創建了一個unique_lock獲取mutex,接着通過隊列非滿條件變量來等待隊列非滿的時候。這裏使用lambda表達式來返回隊列是否已滿,如果條件不滿足,條件變量會釋放mutex,並將線程置於waiting狀態,等待其他線程發出通知喚醒自己;如果條件滿足,則繼續往下執行,往隊列中添加任務,接着發出隊列非空的通知,喚醒一個正在處於等待狀態的線程取走任務。
右值引用
這裏有兩個Push函數,其實它們實現的功能是一樣的,參數爲T&&task是爲了實現移動語句,它其實是一個不定的類型,可能是左值也可能是右值,如果參數是右值,就可以避免對臨時對象的深拷貝,提高性能。
std::forward
一個右值引用參數作爲函數的形參時,在這個函數內部如果再傳遞該參數給其函數,這個參數已經變成了一個左值,不會按照我們預期的類型進行傳遞。std::forward就是爲了實現需要的完美轉發,即保持參數的類型。不管參數T&&這種未定的引用還是明確的左值引用或者右值引用,它會按照參數本來的類型轉發。
std::move
可以將一個左值強制轉換爲一個右值引用,這樣就可以通過移動構造,避免深拷貝。

Pop

void Pop(T& task) {
    std::unique_lock<std::mutex> lock{mutex_};
    not_empty_variable_.wait(lock, [this]{ return !queue_.empty(); });
    task = queue_.front();
    queue_.pop();
    not_full_variable_.notify_one();
}

void Pop(std::queue<T>& tasks) {
    std::unique_lock<std::mutex> lock{mutex_};
    not_empty_variable_.wait(lock, [this]{ return !queue_.empty(); });
    tasks = std::move(queue_);
    not_full_variable_.notify_one();
}

這裏有兩個Pop函數,功能分別爲只獲取一個任務,和獲取全部任務。如果只有獲取一個任務接口,因爲每次獲取任務都需要獲取鎖,想要獲取全部任務就需要調用多次,效率比較低。因此設計多設計了一個獲取全部任務的接口,通過move的方式,將隊列的所有任務都交換出來,避免了數據的複製。
它的流程和Push的過程類似,先獲取mutex,然後通過隊列非空條件變量來等待隊列非空的時候,不滿足時釋放mutex繼續等待,滿足則會將任務從隊列中取出,並喚醒一個在等待添加任務的線程去添加任務。

Empty Full Count Clear

Empty函數用於判斷隊列是否爲空
Full函數用於判斷隊列是否已滿
Count函數用於獲取隊列中的等待執行的任務數量
Clear函數用於清除隊列中等待的任務

鎖和條件變量

std::mutex
獨佔互斥鎖,可以通過lock()方法阻塞線程,直到使用unlock()來解除對互斥量的佔用。
std::lock_guard
只使用std::mutex需要先調用lock()方法鎖,再調用unlock()來釋放鎖。因此,有時候就可能會出現忘記調用unlcok()的情況。而調用std::lock_guard可以簡化鎖的使用,它在構造的時候會自動鎖定互斥鎖,在作用域結束的時候自動地解鎖。
std::condition_variable
條件變量需要和互斥量配合使用,它可以阻塞一個或者多個線程,直到收到其他線程發出的通知或者超時纔會喚醒當前阻塞的線程。

std::unique_lock<std::mutex> lock{mutex_};
not_empty_variable_.wait(lock, [this]{ return !queue_.empty(); });

unique_lockstd::lock_guard是類似的,可以保證安全釋放mutex,區別在於std::lock_guard需要等到作用域結束才釋放mutex,而unique_lock可以自由的釋放mutex。因此可以和條件變量配置使用。
not_empty_variable_就是一個std::condition_variable,它會先檢查隊列非空條件是否滿足,如果滿足則重新獲取mutex,結束wait繼續執行;如果不滿足則會釋放mutex,將線程置爲waiting狀態,等待喚醒。


現在我們已經實現了同步隊列,下面我們將考慮線程池的設計。

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