C++同步線程實(shí)現(xiàn)示例詳解
一、同步線程
雖然使用多線程可以提高應(yīng)用程序的性能,但通常也會(huì)增加復(fù)雜性。如果同時(shí)執(zhí)行多個(gè)函數(shù),則必須同步對(duì)共享資源的訪問(wèn)。一旦應(yīng)用程序達(dá)到一定大小,這將涉及大量的編程工作。本節(jié)介紹Boost.Thread提供的用于同步線程的類。
二、獨(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 提供了不同的互斥類,其中 boost::mutex 是最簡(jiǎn)單的?;コ饬康幕驹硎欠乐蛊渌€程在特定線程擁有互斥量時(shí)取得所有權(quán)。一旦釋放,不同的線程就可以取得所有權(quán)。這會(huì)導(dǎo)致線程等待,直到擁有互斥鎖的線程完成處理并釋放其對(duì)互斥鎖的所有權(quán)。
示例 44.7 使用了一個(gè)名為 mutex 的 boost::mutex 類型的全局互斥體。 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)支持。例如,您可以使用 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í)一樣。類 boost::lock_guard 是 RAII 習(xí)慣用法的一個(gè)示例,用于確保資源在不再需要時(shí)被釋放。
除了 boost::mutex 和 boost::lock_guard,Boost.Thread 還提供了額外的類來(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)在使用類 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 中,互斥量的類型是 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 還支持類 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),則可以使用類型為 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,所以它們都可以使用類型為 boost::shared_lock 的非獨(dú)占鎖同時(shí)訪問(wèn)它。
在 fill() 函數(shù)內(nèi)部,需要一個(gè)類型為 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毀和重新創(chuàng)建鎖。由于互斥量總是通過(guò) wait() 釋放,因此您無(wú)需在迭代結(jié)束時(shí)銷毀鎖。
到此這篇關(guān)于C++同步線程實(shí)現(xiàn)示例詳解的文章就介紹到這了,更多相關(guān)C++同步線程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實(shí)現(xiàn)將輸入的內(nèi)容輸出到文本文件
這篇文章主要介紹了C++實(shí)現(xiàn)將輸入的內(nèi)容輸出到文本文件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08
C語(yǔ)言編程動(dòng)態(tài)內(nèi)存分配常見(jiàn)錯(cuò)誤全面分析
這篇文章主要介紹了C語(yǔ)言編程中動(dòng)態(tài)內(nèi)存分配的常見(jiàn)錯(cuò)誤全面分析講解,同樣遇到過(guò)C語(yǔ)言動(dòng)態(tài)內(nèi)存分配各種問(wèn)題的同學(xué)可以借鑒參考下,希望能夠有所幫助2021-10-10
C語(yǔ)言求兩個(gè)字符串的最長(zhǎng)公共子串
這篇文章主要介紹了C語(yǔ)言求兩個(gè)字符串的最長(zhǎng)公共子串,實(shí)例分析了C語(yǔ)言操作字符串的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02
c#中實(shí)現(xiàn)退出程序后自動(dòng)重新啟動(dòng)程序的方法
下面小編就為大家?guī)?lái)一篇c#中實(shí)現(xiàn)退出程序后自動(dòng)重新啟動(dòng)程序的方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-01-01
C++ 實(shí)現(xiàn)LRU 與 LFU 的緩存算法
設(shè)計(jì)和實(shí)現(xiàn)一個(gè)LRU 緩存機(jī)制。其支持獲取數(shù)據(jù) get 和 寫(xiě)入數(shù)據(jù) put,設(shè)計(jì)并實(shí)現(xiàn)最少訪問(wèn)頻率(LFU)緩存的數(shù)據(jù)結(jié)構(gòu)。LFU的每個(gè)數(shù)據(jù)塊都有一個(gè)引用計(jì)數(shù),所有數(shù)據(jù)塊按照引用計(jì)數(shù)排序,具有相同引用計(jì)數(shù)的數(shù)據(jù)塊則按照時(shí)間進(jìn)行排序。其支持get 和 put,具體了解請(qǐng)看下文2021-09-09
C語(yǔ)言的動(dòng)態(tài)內(nèi)存分配及動(dòng)態(tài)內(nèi)存分配函數(shù)詳解
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言的動(dòng)態(tài)內(nèi)存分配及動(dòng)態(tài)內(nèi)存分配函數(shù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03
C++無(wú)鎖數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)示例詳解
基于C中一個(gè)行壓縮圖的簡(jiǎn)單實(shí)現(xiàn)代碼

