C++同步線程實(shí)現(xiàn)示例詳解
一、同步線程
雖然使用多線程可以提高應(yīng)用程序的性能,但通常也會(huì)增加復(fù)雜性。如果同時(shí)執(zhí)行多個(gè)函數(shù),則必須同步對(duì)共享資源的訪問(wèn)。一旦應(yīng)用程序達(dá)到一定大小,這將涉及大量的編程工作。本節(jié)介紹Boost.Thread提供的用于同步線程的類(lèi)。
二、獨(dú)占訪問(wèn)示例
示例 44.7。使用 boost::mutex 的獨(dú)占訪問(wèn)
#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(); }
多線程程序使用互斥體進(jìn)行同步。 Boost.Thread 提供了不同的互斥類(lèi),其中 boost::mutex 是最簡(jiǎn)單的?;コ饬康幕驹硎欠乐蛊渌€程在特定線程擁有互斥量時(shí)取得所有權(quán)。一旦釋放,不同的線程就可以取得所有權(quán)。這會(huì)導(dǎo)致線程等待,直到擁有互斥鎖的線程完成處理并釋放其對(duì)互斥鎖的所有權(quán)。
示例 44.7 使用了一個(gè)名為 mutex 的 boost::mutex 類(lèi)型的全局互斥體。 thread() 函數(shù)通過(guò)調(diào)用 lock() 獲得此對(duì)象的所有權(quán)。這是在函數(shù)寫(xiě)入標(biāo)準(zhǔn)輸出流之前完成的。寫(xiě)入消息后,通過(guò)調(diào)用 unlock() 釋放所有權(quán)。
main() 創(chuàng)建兩個(gè)線程,這兩個(gè)線程都在執(zhí)行 thread() 函數(shù)。每個(gè)線程計(jì)數(shù)為 5,并在 for 循環(huán)的每次迭代中將消息寫(xiě)入標(biāo)準(zhǔn)輸出流。因?yàn)?std::cout 是線程共享的全局對(duì)象,所以訪問(wèn)必須同步。否則,消息可能會(huì)混淆。同步保證在任何給定時(shí)間,只有一個(gè)線程可以訪問(wèn) std::cout。兩個(gè)線程都嘗試在寫(xiě)入標(biāo)準(zhǔn)輸出流之前獲取互斥鎖,但一次只有一個(gè)線程實(shí)際訪問(wèn) std::cout。無(wú)論哪個(gè)線程成功調(diào)用 lock(),所有其他線程都需要等到 unlock() 被調(diào)用。
獲取和釋放互斥鎖是一個(gè)典型的方案,Boost.Thread通過(guò)不同的類(lèi)型來(lái)支持。例如,您可以使用 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 分別在其構(gòu)造函數(shù)和析構(gòu)函數(shù)中自動(dòng)調(diào)用 lock() 和 unlock()。對(duì)共享資源的訪問(wèn)在示例 44.8 中是同步的,就像顯式調(diào)用兩個(gè)成員函數(shù)時(shí)一樣。類(lèi) boost::lock_guard 是 RAII 習(xí)慣用法的一個(gè)示例,用于確保資源在不再需要時(shí)被釋放。
除了 boost::mutex 和 boost::lock_guard,Boost.Thread 還提供了額外的類(lèi)來(lái)支持同步的變體。其中一個(gè)重要的是 boost::unique_lock ,它提供了幾個(gè)有用的成員函數(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ù)的兩個(gè)變體。兩種變體仍然在循環(huán)中將五個(gè)數(shù)字寫(xiě)入標(biāo)準(zhǔn)輸出流,但它們現(xiàn)在使用類(lèi) boost::unique_lock 來(lái)鎖定互斥鎖。
thread1() 將變量 mutex 傳遞給 boost::unique_lock 的構(gòu)造函數(shù),這使得 boost::unique_lock 嘗試鎖定互斥鎖。在這種情況下,boost::unique_lock 的行為與 boost::lock_guard 沒(méi)有區(qū)別。 boost::unique_lock 的構(gòu)造函數(shù)在互斥量上調(diào)用 lock()。
但是,boost::unique_lock 的析構(gòu)函數(shù)不會(huì)釋放 thread1() 中的互斥量。在 thread1() 中,release() 在鎖上被調(diào)用,這將互斥體與鎖分離。默認(rèn)情況下,boost::unique_lock 的析構(gòu)函數(shù)會(huì)釋放一個(gè)互斥量,就像 boost::lock_guard 的析構(gòu)函數(shù)一樣——但如果互斥量是解耦的則不會(huì)。這就是為什么在 thread1() 中顯式調(diào)用 unlock()。
thread2() 將 mutex 和 boost::try_to_lock 傳遞給 boost::unique_lock 的構(gòu)造函數(shù)。這使得 boost::unique_lock 的構(gòu)造函數(shù)不是在互斥體上調(diào)用 lock(),而是調(diào)用 try_lock()。因此,構(gòu)造函數(shù)只嘗試鎖定互斥量。如果互斥量由另一個(gè)線程擁有,則嘗試失敗。
owns_lock() 可讓您檢測(cè) boost::unique_lock 是否能夠鎖定互斥體。如果 owns_lock() 返回 true,thread2() 可以立即訪問(wèn) std::cout。如果 owns_lock() 返回 false,則調(diào)用 try_lock_for()。此成員函數(shù)也嘗試鎖定互斥鎖,但它會(huì)在失敗前等待互斥鎖一段指定的時(shí)間。在示例 44.9 中,鎖會(huì)嘗試一秒鐘來(lái)獲取互斥量。如果 try_lock_for() 返回 true,則可以訪問(wèn) std::cout。否則,thread2() 放棄并跳過(guò)一個(gè)數(shù)字。因此,示例中的第二個(gè)線程可能不會(huì)將五個(gè)數(shù)字寫(xiě)入標(biāo)準(zhǔn)輸出流。
請(qǐng)注意,在示例 44.9 中,互斥量的類(lèi)型是 boost::timed_mutex,而不是 boost::mutex。該示例使用 boost::timed_mutex,因?yàn)榇嘶コ饬渴俏ㄒ惶峁┏蓡T函數(shù) try_lock_for() 的互斥量。當(dāng)對(duì)鎖調(diào)用 try_lock_for() 時(shí)調(diào)用此成員函數(shù)。 boost::mutex 僅提供成員函數(shù) lock() 和 try_lock()。
boost::unique_lock 是一個(gè)獨(dú)占鎖。獨(dú)占鎖始終是互斥量的唯一所有者。另一個(gè)鎖只有在排他鎖釋放后才能獲得互斥鎖的控制權(quán)。 Boost.Thread 還支持類(lèi) 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'; }
如果線程只需要對(duì)特定資源進(jìn)行只讀訪問(wèn),則可以使用類(lèi)型為 boost::shared_lock 的非獨(dú)占鎖。修改資源的線程需要寫(xiě)訪問(wèn)權(quán),因此需要獨(dú)占鎖。由于具有只讀訪問(wèn)權(quán)限的線程不受同時(shí)讀取同一資源的其他線程的影響,因此它可以使用非排他鎖并共享互斥鎖。
在示例 44.10 中,print() 和 count() 都只讀取變量 random_numbers。 print() 函數(shù)將 random_numbers 中的最后一個(gè)值寫(xiě)入標(biāo)準(zhǔn)輸出流,count() 函數(shù)將其添加到變量 sum 中。因?yàn)閮蓚€(gè)函數(shù)都不修改 random_numbers,所以它們都可以使用類(lèi)型為 boost::shared_lock 的非獨(dú)占鎖同時(shí)訪問(wèn)它。
在 fill() 函數(shù)內(nèi)部,需要一個(gè)類(lèi)型為 boost::unique_lock 的獨(dú)占鎖,因?yàn)樗鼘⑿碌碾S機(jī)數(shù)插入到 random_numbers 中。 fill() 使用 unlock() 成員函數(shù)釋放互斥鎖,然后等待一秒鐘。與前面的示例不同,wait() 在 for 循環(huán)的末尾調(diào)用,以保證在 print() 或 count() 訪問(wèn)容器之前至少將一個(gè)隨機(jī)數(shù)放入容器中。這兩個(gè)函數(shù)都在它們的 for 循環(huán)開(kāi)始時(shí)調(diào)用 wait() 函數(shù)。
查看從不同位置對(duì) wait() 函數(shù)的單獨(dú)調(diào)用,一個(gè)潛在問(wèn)題變得明顯:函數(shù)調(diào)用的順序直接受到 CPU 實(shí)際執(zhí)行各個(gè)線程的順序的影響。使用條件變量,可以同步各個(gè)線程,以便添加到 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ù)。線程不再在每次迭代中等待一秒鐘;相反,它們會(huì)盡可能快地執(zhí)行。此外,不計(jì)算總數(shù);數(shù)字只是寫(xiě)入標(biāo)準(zhǔn)輸出流。
為了確保隨機(jī)數(shù)的正確處理,各個(gè)線程使用條件變量進(jìn)行同步,可以檢查多個(gè)線程之間的某些條件。
和以前一樣,fill() 函數(shù)在每次迭代時(shí)生成一個(gè)隨機(jī)數(shù),并將其放入 random_numbers 容器中。為了阻止其他線程同時(shí)訪問(wèn)容器,使用了排他鎖。這個(gè)例子沒(méi)有等待一秒鐘,而是使用了一個(gè)條件變量。調(diào)用 notify_all() 將喚醒一直在使用 wait() 等待此通知的每個(gè)線程。
查看 print() 函數(shù)的 for 循環(huán),您可以看到為相同的條件變量調(diào)用了成員函數(shù) wait()。當(dāng)線程被調(diào)用 notify_all() 喚醒時(shí),它會(huì)嘗試獲取互斥鎖,只有在 fill() 函數(shù)中成功釋放互斥鎖后才會(huì)成功。
這里的技巧是調(diào)用 wait() 也會(huì)釋放作為參數(shù)傳遞的互斥體。調(diào)用 notify_all() 后,fill() 函數(shù)通過(guò)調(diào)用 wait() 釋放互斥量。然后它會(huì)阻塞并等待其他線程調(diào)用 notify_all(),一旦隨機(jī)數(shù)被寫(xiě)入標(biāo)準(zhǔn)輸出流,它就會(huì)在 print() 函數(shù)中發(fā)生。
請(qǐng)注意,對(duì) print() 函數(shù)內(nèi)的 wait() 成員函數(shù)的調(diào)用實(shí)際上發(fā)生在單獨(dú)的 while 循環(huán)中。這樣做是為了處理在 print() 中首次調(diào)用 wait() 成員函數(shù)之前已經(jīng)將隨機(jī)數(shù)放入容器中的情況。通過(guò)將 random_numbers 中存儲(chǔ)的元素?cái)?shù)量與預(yù)期的元素?cái)?shù)量進(jìn)行比較,成功處理了這種情況,并將隨機(jī)數(shù)寫(xiě)入標(biāo)準(zhǔn)輸出流。
如果鎖不是在 for 循環(huán)中的本地鎖而是在外部作用域中實(shí)例化,則示例 44.11 也適用。事實(shí)上,這更有意義,因?yàn)椴恍枰诿看蔚卸间N(xiāo)毀和重新創(chuàng)建鎖。由于互斥量總是通過(guò) wait() 釋放,因此您無(wú)需在迭代結(jié)束時(shí)銷(xiāo)毀鎖。
到此這篇關(guān)于C++同步線程實(shí)現(xiàn)示例詳解的文章就介紹到這了,更多相關(guān)C++同步線程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++中SetConsoleCursorPosition()移動(dòng)光標(biāo)函數(shù)的用法大全
這篇文章主要介紹了C++中SetConsoleCursorPosition()移動(dòng)光標(biāo)函數(shù)的用法大全,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03C++中的explicit關(guān)鍵字實(shí)例淺析
在C++程序中很少有人去使用explicit關(guān)鍵字,不可否認(rèn),在平時(shí)的實(shí)踐中確實(shí)很少能用的上,再說(shuō)C++的功能強(qiáng)大,往往一個(gè)問(wèn)題可以利用好幾種C++特性去解決。接下來(lái)給大家介紹 C++中的explicit關(guān)鍵字,需要的朋友可以參考下2017-03-03Linux中使用C語(yǔ)言的fork()函數(shù)創(chuàng)建子進(jìn)程的實(shí)例教程
fork是一個(gè)在Linux系統(tǒng)環(huán)境下專有的函數(shù),現(xiàn)有的進(jìn)程調(diào)用fork后將會(huì)創(chuàng)建一個(gè)新的進(jìn)程,這里我們就來(lái)看一下Linux中使用C語(yǔ)言的fork()函數(shù)創(chuàng)建子進(jìn)程的實(shí)例教程2016-06-06C++ Log日志類(lèi)輕量級(jí)支持格式化輸出變量實(shí)現(xiàn)代碼
這篇文章主要介紹了C++ Log日志類(lèi)輕量級(jí)支持格式化輸出變量實(shí)現(xiàn)代碼,需要的朋友可以參考下2019-04-04C語(yǔ)言求解最長(zhǎng)公共子字符串問(wèn)題及相關(guān)的算法分析
最長(zhǎng)公共子字符串問(wèn)題即是求一個(gè)字符串在另一個(gè)字符串中出現(xiàn)的連續(xù)最多字符,這里我們來(lái)看一下面試中經(jīng)常出現(xiàn)的C語(yǔ)言求解最長(zhǎng)公共子字符串問(wèn)題及相關(guān)的算法分析2016-06-06QT設(shè)置widget背景圖片不影響widget內(nèi)其他控件背景的方法
這篇文章主要給大家介紹了關(guān)于QT設(shè)置widget背景圖片不影響widget內(nèi)其他控件背景的方法,軟件的界面為了更直觀或美觀,常常需要通過(guò)圖片來(lái)表達(dá),需要的朋友可以參考下2023-06-06C語(yǔ)言動(dòng)態(tài)開(kāi)辟內(nèi)存詳解
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言動(dòng)態(tài)開(kāi)辟內(nèi)存,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-02-02