C++中多線程間共享數(shù)據(jù)詳解
在 C++ 中,我們可以通過構(gòu)造 std::mutex (mutual exclusion)的實(shí)例來創(chuàng)建互斥,調(diào)用成員函數(shù) lock() 對(duì)其加鎖,調(diào)用 unlock() 解鎖。但是不推薦直接調(diào)用成員函數(shù)的做法,因?yàn)檫@樣做,那我們就必須在該函數(shù)的每條路徑上都調(diào)用 unlock(),包括異常導(dǎo)致退出的路徑。取而代之,C++標(biāo)準(zhǔn)庫提供了類模板 std::lock_guard<>,針對(duì)互斥類融合RAII手法:在構(gòu)造的時(shí)候給互斥加鎖,在析構(gòu)時(shí)進(jìn)行解鎖,從而保證互斥總被正確解鎖。
std::mutex的基本用法
std::list<int> some_list; std::mutex some_mutex; void add_to_list(int new_value){ std::lock_guard<std::mutex> guard(some_mutex); some_list.push_back(new_value); } bool list_contains(int value_to_find){ std::lock_guard<std::mutex> guard(some_mutex); return std::find(some_list.begin(), some_list.end(), value_to_find) != some_list.end(); }
防范死鎖
防范死鎖的通常建議是,始終按相同順序?qū)蓚€(gè)互斥加鎖。若我們總是先鎖互斥A,再鎖互斥B,則永遠(yuǎn)不會(huì)發(fā)生死鎖。但是有時(shí)會(huì)出現(xiàn)一些棘手的問題,例如,一個(gè)函數(shù),其操作同一個(gè)類的兩個(gè)實(shí)例,交換它們內(nèi)部的數(shù)據(jù)為了保證并發(fā)時(shí),免受其他改動(dòng)的影響,需要對(duì)它們進(jìn)行加鎖,但是函數(shù)入?yún)⒌捻樞蚩梢愿淖?,即可以是swap(A,B)也可以是swap(B,A),當(dāng)同時(shí)發(fā)生這種情況時(shí),死鎖也會(huì)發(fā)生。因此需要一種方式來,來同時(shí)鎖住兩個(gè)互斥,而不是誰先誰后。即要求“全員共同成敗”(all-or-nothing,或全部成功鎖定,或沒獲取任何鎖并拋出異常)的語義。
使用 std::lock() 函數(shù),同時(shí)鎖住互斥。
class some_big_object; void swap(some_big_object& lhs, some_big_object& rhs); class X{ private: some_big_object some_detail; std::mutex m; public: X(const some_big_object &sd):some_detail(sd){} friend void swap(X& lhs, X& rhs){ if(&lhs == &rhs){ return; } std::lock(lhs.m, rhs.m); std::lock_guard lock_a(lhs.m, std::adopt_lock); std::lock_guard lock_b(rhs.m, std::adopt_lock); swap(lhs.some_detail, rhs.some_detail); } }
std::lock() 是一個(gè)函數(shù),并不像 RAII 類一樣,在析構(gòu)時(shí)進(jìn)行解鎖。因此需要借助 std::lock_guard 類型的對(duì)象,讓 std::mutex 在函數(shù)完成后進(jìn)行解鎖。std::adopt_lock 作為函數(shù)參數(shù),表示 std::mutex 已經(jīng)被鎖住了,讓std::lock_guard 類不需要在構(gòu)造函數(shù)中對(duì) std::mutex 進(jìn)行加鎖。C++ 17出現(xiàn)了 std::scoped_lock<> 模板類,其和 std::lock_guard<>完全等價(jià),只不過前者是可變參數(shù)模板,接受各種互斥型別作為模板參數(shù),還以多個(gè)互斥對(duì)象作為構(gòu)造函數(shù)的參數(shù)列表,除了這些其還有與 std::lock() 函數(shù)一樣的功能,因此可以改善上述代碼。
void swap(X& lhs, X& rhs){ if(&lhs == &rhs){ return; } std::scoped_lock guard(lhs.m, rhs.m); swap(lhs.some_detail, rhs.some_detail); }
std::lock_guard 和 std::unique_lock
std::unique_lock 類支持在構(gòu)造時(shí)暫時(shí)不獲得鎖,在需要的時(shí)候手動(dòng)調(diào)用 lock(),而獲得鎖。其含有一個(gè)內(nèi)部標(biāo)志 __owns__(可以通過其成員函數(shù) owns_lock() 獲得),表明關(guān)聯(lián)的互斥目前是否正被該類的實(shí)例上鎖。假如 std::unique_lock 實(shí)例關(guān)聯(lián)的互斥的確上鎖了,則其析構(gòu)函數(shù)必須調(diào)用unlock();若不然,實(shí)例并未將關(guān)聯(lián)的互斥上鎖,便絕不能調(diào)用 unlock()。
初始化時(shí)保護(hù)共享數(shù)據(jù)
void undefined_behaviour_with_double_checked_locking() { if(!resource_ptr){ //① std::lock_guard<std::mutex> lk(resource_mutex); // ② if(!resource_ptr){ resource_ptr.reset(new some_resource); } } resource_ptr->do_something(); }
在雙重檢查鎖定模式中,一號(hào)線程執(zhí)行到①發(fā)現(xiàn)條件滿足(resource_ptr 為空),同時(shí)二號(hào)線程執(zhí)行到①也發(fā)現(xiàn)條件滿足,然后繼續(xù)執(zhí)行上鎖操作②,此時(shí)一號(hào)線程也會(huì)去執(zhí)行上鎖操作,但是resource_mutex上的鎖已經(jīng)被二號(hào)線程持有了,這樣就會(huì)發(fā)生數(shù)據(jù)競爭(當(dāng)然會(huì)發(fā)生惡性數(shù)據(jù)競爭的路徑不止這一條)。
為了解決這種情況,C++標(biāo)準(zhǔn)庫中提供了 std::onec_flag 類 和 std::call_once 類。
std::shared_ptr<some_resource> resource_ptr; std::once_flag resource_flag; void init_resource() { resource_ptr.reset(new some_resource); } void foo() { std::call_once(resource_flag, init_resource); resource_ptr->do_something(); }
保護(hù)讀多寫少的數(shù)據(jù)
C++ 17 標(biāo)準(zhǔn)庫提供了兩種新的互斥:std::shared_mutex 和 std::shared_timed_mutex(C++14就有了)。兩者的區(qū)別在于后者支持更多操作。
std::shared_mutex mutex; // 獲取讀鎖(又叫共享鎖) std::shared_lock read_lock(mutex); // 獲取寫鎖(又叫排它鎖) std::lock_guard write_lock(mutex);
假設(shè)共享鎖已經(jīng)被某些線程持有,若別的線程試圖獲取排他鎖,就會(huì)發(fā)生阻塞,直到這些線程全都釋放該共享鎖。反之,如果任一線程持有排他鎖,那么其他線程都無法獲取共享鎖或排它鎖,直到持鎖線程將排它鎖釋放為止。
到此這篇關(guān)于C++中多線程間共享數(shù)據(jù)詳解的文章就介紹到這了,更多相關(guān)C++共享數(shù)據(jù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何使用C++結(jié)合OpenCV進(jìn)行圖像處理與分類
在計(jì)算機(jī)視覺領(lǐng)域,OpenCV與C++結(jié)合能高效處理和分類圖像,C++的高執(zhí)行效率適合大規(guī)模數(shù)據(jù)處理,OpenCV提供豐富的功能,如圖像預(yù)處理和機(jī)器學(xué)習(xí)算法,安裝OpenCV需要配置環(huán)境和添加庫文件,本文詳細(xì)介紹了使用C++和OpenCV進(jìn)行圖像分類的過程,包括使用SVM和深度學(xué)習(xí)模型2024-09-09C++實(shí)現(xiàn)鄰接表頂點(diǎn)的刪除
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)鄰接表頂點(diǎn)的刪除,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04C++用read()和write()讀寫二進(jìn)制文件的超詳細(xì)教程
二進(jìn)制的文件肉眼我們是讀不懂的,如果通過二進(jìn)制的讀寫操作就可以讀懂,下面這篇文章主要給大家介紹了關(guān)于C++用read()和write()讀寫二進(jìn)制文件的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06C語言fprintf()函數(shù)和fscanf()函數(shù)的具體使用
本文主要介紹了C語言fprintf()函數(shù)和fscanf()函數(shù)的具體使用,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11VS2017+Qt5+Opencv3.4調(diào)用攝像頭拍照并存儲(chǔ)
本文主要介紹了VS2017+Qt5+Opencv3.4調(diào)用攝像頭拍照并存儲(chǔ),實(shí)現(xiàn)了視頻,拍照,保存這三個(gè)功能。具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05Visual Studio Code (vscode) 配置C、C++環(huán)境/編寫運(yùn)行C、C++的教程詳解(主要Windo
這篇文章主要介紹了Visual Studio Code (vscode) 配置C、C++環(huán)境/編寫運(yùn)行C、C++(主要Windows、簡要Linux),本文通過實(shí)例截圖給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03