C++11保护共享数据的其他方法
保護共享數據的初始化過程
在多線程編程中,互斥量是最通用的保護共享數據的機制。但是在某些情況下,一些資源僅需要在第一次初始化的時候需要保護,其時候就可以不需要互斥變量的保護了。比如編碼中最常見的單例模式,核心代碼如下:
//(3)獲得本類實例的唯一全局訪問點 static CSinglton* GetInstance() {//若實例不存在,則創建實例對象if (NULL == pInstance){pInstance = new CSinglton();} //實例已經存在,直接該實例對象return pInstance; }以上代碼在多線程環境中是不安全的,有可能導致創建對象兩次,導致內存泄漏;如果每次訪問之前都是加鎖,將大大影響訪問性能,即使能解決問題。
為了滿足多線程環境中高效和安全的特性,人們提出了雙重鎖定方式單例模式,代碼如下:
static CSinglton* GetInstance() {//僅在實例未被創建時加鎖,其他時候直接返回if (NULL == pInstance){std::lock_guard<std::mutex> guard(g_mutex)if (NULL == pInstance){//若實例不存在,則創建實例對象pInstance = new CSinglton();//步驟一}}//實例已經存在,直接該實例對象return pInstance; } //執行代碼 GetInstance()->dosomething();//步驟二但是在實踐中可以發現,會因為指令編排方式不同,導致一些異常情況發生。
因為線程A中pInstance分配了指針,CSinglton的構造函數還沒有執行,但此時線程B開始調用GetInstance()接口,因為pInstance已經非空,直接執行dosomething(),就導致異常情況發生了。
同樣的,創建靜態局部變量的代碼,在C++11之前也存在搶著創建變量的問題:
?
std::call_once保護共享數據
為了解決雙重鎖定以及創建多個實例的問題,C++11標準庫提供了std::once_flag和std::call_once來處理只初始化一次的情況。使用std::call_once比顯式使用互斥量消耗的資源更少,并且還是無鎖式編程,減少了死鎖問題的發生。
call_once和once_flag的用法:
std::once_flag flag;void simple_do_once() {std::call_once(flag, [](){ std::cout << "Simple example: called once\n"; }); }int _tmain(int argc, _TCHAR* argv[]) {std::thread st1(simple_do_once);std::thread st2(simple_do_once);std::thread st3(simple_do_once);std::thread st4(simple_do_once);st1.join();st2.join();st3.join();st4.join();std::cout << "main thread end\n"; }運行結果:
Simple example: called once//只有一個打印,目標函數僅被執行一次 main thread end如何用std::call_once創建安全性的單例模式參見這篇文章call_once 使用方法
發現問題
在正常情況下,若在執行期間call_once調用的函數拋出異常,則once_flag狀態不會翻轉,其他線程還可以繼續執行call_once;但是在vs2013測試發現,異常無法安裝預期進行,不知道是何種原因,這里做個記錄。
?測試代碼:
std::once_flag flag;void may_throw_function(bool do_throw) {if (do_throw) {std::cout << "throw: call_once will retry\n"; // 這會出現多于一次throw std::exception();}std::cout << "Didn't throw, call_once will not attempt again\n"; // 保證一次 }void do_once(bool do_throw) {try {std::call_once(flag, may_throw_function, do_throw);}catch (...) {} }int main(int argc, _TCHAR* argv[]) {std::thread t1(do_once, true);std::thread t2(do_once, true);std::thread t3(do_once, false);std::thread t4(do_once, true);t1.join();t2.join();t3.join();t4.join();std::cout << "main thread end\n"; }運行結果:
vs2013編譯的程序無法繼續執行,線程卡住,如下
td::call_once的替代方案
在前面的文章中提到,我們也可以使用靜態局部變量的方式創建唯一實例,只是在多線程環境中,存在這么一種情況:每個線程都認為他們是第一個初始化這個變量,導致這個變量被創建兩次。
但在C++11標準中,這些問題都被解決了:初始化及定義完全在一個線程中發生,并且沒有其他線程可在初始化完成前對其進行處理,條件競爭終止于初始化階段。當然,這個要求編譯要支持C++11才可以。
?
本文轉自:C++11保護共享數據的其他方法_Keep Moving~-CSDN博客
總結
以上是生活随笔為你收集整理的C++11保护共享数据的其他方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 谷歌云 CEO 呼吁员工不要理会对手批评
- 下一篇: 5999元起!OPPO Find N2