C++同步線程實現(xiàn)示例詳解
一、同步線程
雖然使用多線程可以提高應用程序的性能,但通常也會增加復雜性。如果同時執(zhí)行多個函數(shù),則必須同步對共享資源的訪問。一旦應用程序達到一定大小,這將涉及大量的編程工作。本節(jié)介紹Boost.Thread提供的用于同步線程的類。
二、獨占訪問示例
示例 44.7。使用 boost::mutex 的獨占訪問
#include <boost/thread.hpp> #include <boost/chrono.hpp> #include <iostream> void wait(int seconds) { boost::this_thread::sleep_for(boost::chrono::seconds{seconds}); } boost::mutex mutex; void thread() { using boost::this_thread::get_id; for (int i = 0; i < 5; ++i) { wait(1); mutex.lock(); std::cout << "Thread " << get_id() << ": " << i << std::endl; mutex.unlock(); } } int main() { boost::thread t1{thread}; boost::thread t2{thread}; t1.join(); t2.join(); }
多線程程序使用互斥體進行同步。 Boost.Thread 提供了不同的互斥類,其中 boost::mutex 是最簡單的?;コ饬康幕驹硎欠乐蛊渌€程在特定線程擁有互斥量時取得所有權。一旦釋放,不同的線程就可以取得所有權。這會導致線程等待,直到擁有互斥鎖的線程完成處理并釋放其對互斥鎖的所有權。
示例 44.7 使用了一個名為 mutex 的 boost::mutex 類型的全局互斥體。 thread() 函數(shù)通過調用 lock() 獲得此對象的所有權。這是在函數(shù)寫入標準輸出流之前完成的。寫入消息后,通過調用 unlock() 釋放所有權。
main() 創(chuàng)建兩個線程,這兩個線程都在執(zhí)行 thread() 函數(shù)。每個線程計數(shù)為 5,并在 for 循環(huán)的每次迭代中將消息寫入標準輸出流。因為 std::cout 是線程共享的全局對象,所以訪問必須同步。否則,消息可能會混淆。同步保證在任何給定時間,只有一個線程可以訪問 std::cout。兩個線程都嘗試在寫入標準輸出流之前獲取互斥鎖,但一次只有一個線程實際訪問 std::cout。無論哪個線程成功調用 lock(),所有其他線程都需要等到 unlock() 被調用。
獲取和釋放互斥鎖是一個典型的方案,Boost.Thread通過不同的類型來支持。例如,您可以使用 boost::lock_guard 而不是使用 lock() 和 unlock()。
示例 44.8。 boost::lock_guard 保證互斥釋放
#include <boost/thread.hpp> #include <boost/chrono.hpp> #include <iostream> void wait(int seconds) { boost::this_thread::sleep_for(boost::chrono::seconds{seconds}); } boost::mutex mutex; void thread() { using boost::this_thread::get_id; for (int i = 0; i < 5; ++i) { wait(1); boost::lock_guard<boost::mutex> lock{mutex}; std::cout << "Thread " << get_id() << ": " << i << std::endl; } } int main() { boost::thread t1{thread}; boost::thread t2{thread}; t1.join(); t2.join(); }
boost::lock_guard 分別在其構造函數(shù)和析構函數(shù)中自動調用 lock() 和 unlock()。對共享資源的訪問在示例 44.8 中是同步的,就像顯式調用兩個成員函數(shù)時一樣。類 boost::lock_guard 是 RAII 習慣用法的一個示例,用于確保資源在不再需要時被釋放。
除了 boost::mutex 和 boost::lock_guard,Boost.Thread 還提供了額外的類來支持同步的變體。其中一個重要的是 boost::unique_lock ,它提供了幾個有用的成員函數(shù)。
示例 44.9。多功能鎖boost::unique_lock
#include <boost/thread.hpp> #include <boost/chrono.hpp> #include <iostream> void wait(int seconds) { boost::this_thread::sleep_for(boost::chrono::seconds{seconds}); } boost::timed_mutex mutex; void thread1() { using boost::this_thread::get_id; for (int i = 0; i < 5; ++i) { wait(1); boost::unique_lock<boost::timed_mutex> lock{mutex}; std::cout << "Thread " << get_id() << ": " << i << std::endl; boost::timed_mutex *m = lock.release(); m->unlock(); } } void thread2() { using boost::this_thread::get_id; for (int i = 0; i < 5; ++i) { wait(1); boost::unique_lock<boost::timed_mutex> lock{mutex, boost::try_to_lock}; if (lock.owns_lock() || lock.try_lock_for(boost::chrono::seconds{1})) { std::cout << "Thread " << get_id() << ": " << i << std::endl; } } } int main() { boost::thread t1{thread1}; boost::thread t2{thread2}; t1.join(); t2.join(); }
示例 44.9 使用了 thread() 函數(shù)的兩個變體。兩種變體仍然在循環(huán)中將五個數(shù)字寫入標準輸出流,但它們現(xiàn)在使用類 boost::unique_lock 來鎖定互斥鎖。
thread1() 將變量 mutex 傳遞給 boost::unique_lock 的構造函數(shù),這使得 boost::unique_lock 嘗試鎖定互斥鎖。在這種情況下,boost::unique_lock 的行為與 boost::lock_guard 沒有區(qū)別。 boost::unique_lock 的構造函數(shù)在互斥量上調用 lock()。
但是,boost::unique_lock 的析構函數(shù)不會釋放 thread1() 中的互斥量。在 thread1() 中,release() 在鎖上被調用,這將互斥體與鎖分離。默認情況下,boost::unique_lock 的析構函數(shù)會釋放一個互斥量,就像 boost::lock_guard 的析構函數(shù)一樣——但如果互斥量是解耦的則不會。這就是為什么在 thread1() 中顯式調用 unlock()。
thread2() 將 mutex 和 boost::try_to_lock 傳遞給 boost::unique_lock 的構造函數(shù)。這使得 boost::unique_lock 的構造函數(shù)不是在互斥體上調用 lock(),而是調用 try_lock()。因此,構造函數(shù)只嘗試鎖定互斥量。如果互斥量由另一個線程擁有,則嘗試失敗。
owns_lock() 可讓您檢測 boost::unique_lock 是否能夠鎖定互斥體。如果 owns_lock() 返回 true,thread2() 可以立即訪問 std::cout。如果 owns_lock() 返回 false,則調用 try_lock_for()。此成員函數(shù)也嘗試鎖定互斥鎖,但它會在失敗前等待互斥鎖一段指定的時間。在示例 44.9 中,鎖會嘗試一秒鐘來獲取互斥量。如果 try_lock_for() 返回 true,則可以訪問 std::cout。否則,thread2() 放棄并跳過一個數(shù)字。因此,示例中的第二個線程可能不會將五個數(shù)字寫入標準輸出流。
請注意,在示例 44.9 中,互斥量的類型是 boost::timed_mutex,而不是 boost::mutex。該示例使用 boost::timed_mutex,因為此互斥量是唯一提供成員函數(shù) try_lock_for() 的互斥量。當對鎖調用 try_lock_for() 時調用此成員函數(shù)。 boost::mutex 僅提供成員函數(shù) lock() 和 try_lock()。
boost::unique_lock 是一個獨占鎖。獨占鎖始終是互斥量的唯一所有者。另一個鎖只有在排他鎖釋放后才能獲得互斥鎖的控制權。 Boost.Thread 還支持類 boost::shared_lock 的共享鎖,它與 shared_mutex 一起使用。
示例 44.10。與 boost::shared_lock 共享鎖
#include <boost/thread.hpp> #include <boost/chrono.hpp> #include <iostream> #include <vector> #include <cstdlib> #include <ctime> void wait(int seconds) { boost::this_thread::sleep_for(boost::chrono::seconds{seconds}); } boost::shared_mutex mutex; std::vector<int> random_numbers; void fill() { std::srand(static_cast<unsigned int>(std::time(0))); for (int i = 0; i < 3; ++i) { boost::unique_lock<boost::shared_mutex> lock{mutex}; random_numbers.push_back(std::rand()); lock.unlock(); wait(1); } } void print() { for (int i = 0; i < 3; ++i) { wait(1); boost::shared_lock<boost::shared_mutex> lock{mutex}; std::cout << random_numbers.back() << '\n'; } } int sum = 0; void count() { for (int i = 0; i < 3; ++i) { wait(1); boost::shared_lock<boost::shared_mutex> lock{mutex}; sum += random_numbers.back(); } } int main() { boost::thread t1{fill}, t2{print}, t3{count}; t1.join(); t2.join(); t3.join(); std::cout << "Sum: " << sum << '\n'; }
如果線程只需要對特定資源進行只讀訪問,則可以使用類型為 boost::shared_lock 的非獨占鎖。修改資源的線程需要寫訪問權,因此需要獨占鎖。由于具有只讀訪問權限的線程不受同時讀取同一資源的其他線程的影響,因此它可以使用非排他鎖并共享互斥鎖。
在示例 44.10 中,print() 和 count() 都只讀取變量 random_numbers。 print() 函數(shù)將 random_numbers 中的最后一個值寫入標準輸出流,count() 函數(shù)將其添加到變量 sum 中。因為兩個函數(shù)都不修改 random_numbers,所以它們都可以使用類型為 boost::shared_lock 的非獨占鎖同時訪問它。
在 fill() 函數(shù)內部,需要一個類型為 boost::unique_lock 的獨占鎖,因為它將新的隨機數(shù)插入到 random_numbers 中。 fill() 使用 unlock() 成員函數(shù)釋放互斥鎖,然后等待一秒鐘。與前面的示例不同,wait() 在 for 循環(huán)的末尾調用,以保證在 print() 或 count() 訪問容器之前至少將一個隨機數(shù)放入容器中。這兩個函數(shù)都在它們的 for 循環(huán)開始時調用 wait() 函數(shù)。
查看從不同位置對 wait() 函數(shù)的單獨調用,一個潛在問題變得明顯:函數(shù)調用的順序直接受到 CPU 實際執(zhí)行各個線程的順序的影響。使用條件變量,可以同步各個線程,以便添加到 random_numbers 的值立即由不同的線程處理。
示例 44.11。帶有 boost::condition_variable_any 的條件變量
#include <boost/thread.hpp> #include <iostream> #include <vector> #include <cstdlib> #include <ctime> boost::mutex mutex; boost::condition_variable_any cond; std::vector<int> random_numbers; void fill() { std::srand(static_cast<unsigned int>(std::time(0))); for (int i = 0; i < 3; ++i) { boost::unique_lock<boost::mutex> lock{mutex}; random_numbers.push_back(std::rand()); cond.notify_all(); cond.wait(mutex); } } void print() { std::size_t next_size = 1; for (int i = 0; i < 3; ++i) { boost::unique_lock<boost::mutex> lock{mutex}; while (random_numbers.size() != next_size) cond.wait(mutex); std::cout << random_numbers.back() << '\n'; ++next_size; cond.notify_all(); } } int main() { boost::thread t1{fill}; boost::thread t2{print}; t1.join(); t2.join(); }
示例 44.11 刪除了 wait() 和 count() 函數(shù)。線程不再在每次迭代中等待一秒鐘;相反,它們會盡可能快地執(zhí)行。此外,不計算總數(shù);數(shù)字只是寫入標準輸出流。
為了確保隨機數(shù)的正確處理,各個線程使用條件變量進行同步,可以檢查多個線程之間的某些條件。
和以前一樣,fill() 函數(shù)在每次迭代時生成一個隨機數(shù),并將其放入 random_numbers 容器中。為了阻止其他線程同時訪問容器,使用了排他鎖。這個例子沒有等待一秒鐘,而是使用了一個條件變量。調用 notify_all() 將喚醒一直在使用 wait() 等待此通知的每個線程。
查看 print() 函數(shù)的 for 循環(huán),您可以看到為相同的條件變量調用了成員函數(shù) wait()。當線程被調用 notify_all() 喚醒時,它會嘗試獲取互斥鎖,只有在 fill() 函數(shù)中成功釋放互斥鎖后才會成功。
這里的技巧是調用 wait() 也會釋放作為參數(shù)傳遞的互斥體。調用 notify_all() 后,fill() 函數(shù)通過調用 wait() 釋放互斥量。然后它會阻塞并等待其他線程調用 notify_all(),一旦隨機數(shù)被寫入標準輸出流,它就會在 print() 函數(shù)中發(fā)生。
請注意,對 print() 函數(shù)內的 wait() 成員函數(shù)的調用實際上發(fā)生在單獨的 while 循環(huán)中。這樣做是為了處理在 print() 中首次調用 wait() 成員函數(shù)之前已經(jīng)將隨機數(shù)放入容器中的情況。通過將 random_numbers 中存儲的元素數(shù)量與預期的元素數(shù)量進行比較,成功處理了這種情況,并將隨機數(shù)寫入標準輸出流。
如果鎖不是在 for 循環(huán)中的本地鎖而是在外部作用域中實例化,則示例 44.11 也適用。事實上,這更有意義,因為不需要在每次迭代中都銷毀和重新創(chuàng)建鎖。由于互斥量總是通過 wait() 釋放,因此您無需在迭代結束時銷毀鎖。
到此這篇關于C++同步線程實現(xiàn)示例詳解的文章就介紹到這了,更多相關C++同步線程內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++中SetConsoleCursorPosition()移動光標函數(shù)的用法大全
這篇文章主要介紹了C++中SetConsoleCursorPosition()移動光標函數(shù)的用法大全,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03Linux中使用C語言的fork()函數(shù)創(chuàng)建子進程的實例教程
fork是一個在Linux系統(tǒng)環(huán)境下專有的函數(shù),現(xiàn)有的進程調用fork后將會創(chuàng)建一個新的進程,這里我們就來看一下Linux中使用C語言的fork()函數(shù)創(chuàng)建子進程的實例教程2016-06-06C++ Log日志類輕量級支持格式化輸出變量實現(xiàn)代碼
這篇文章主要介紹了C++ Log日志類輕量級支持格式化輸出變量實現(xiàn)代碼,需要的朋友可以參考下2019-04-04QT設置widget背景圖片不影響widget內其他控件背景的方法
這篇文章主要給大家介紹了關于QT設置widget背景圖片不影響widget內其他控件背景的方法,軟件的界面為了更直觀或美觀,常常需要通過圖片來表達,需要的朋友可以參考下2023-06-06