亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C++ > 正文

C++多線程編程時的數據保護

2020-05-23 14:18:38
字體:
來源:轉載
供稿:網友

這篇文章主要介紹了C++多線程編程時的數據保護,作者針對C++11版本中的新特性做出了一些解說,需要的朋友可以參考下

在編寫多線程程序時,多個線程同時訪問某個共享資源,會導致同步的問題,這篇文章中我們將介紹 C++11 多線程編程中的數據保護。

數據丟失

讓我們從一個簡單的例子開始,請看如下代碼:

 

 
  1. #include <iostream> 
  2. #include <string> 
  3. #include <thread> 
  4. #include <vector> 
  5.  
  6. using std::thread; 
  7. using std::vector; 
  8. using std::cout; 
  9. using std::endl; 
  10.  
  11. class Incrementer 
  12. private
  13. int counter; 
  14.  
  15. public
  16. Incrementer() : counter{0} { }; 
  17.  
  18. void operator()() 
  19. for(int i = 0; i < 100000; i++) 
  20. this->counter++; 
  21.  
  22. int getCounter() const 
  23. return this->counter; 
  24. }  
  25. }; 
  26.  
  27. int main() 
  28. // Create the threads which will each do some counting 
  29. vector<thread> threads; 
  30.  
  31. Incrementer counter; 
  32.  
  33. threads.push_back(thread(std::ref(counter))); 
  34. threads.push_back(thread(std::ref(counter))); 
  35. threads.push_back(thread(std::ref(counter))); 
  36.  
  37. for(auto &t : threads) 
  38. t.join(); 
  39.  
  40. cout << counter.getCounter() << endl; 
  41.  
  42. return 0; 

這個程序的目的就是數數,數到30萬,某些傻叉程序員想要優化數數的過程,因此創建了三個線程,使用一個共享變量 counter,每個線程負責給這個變量增加10萬計數。

這段代碼創建了一個名為 Incrementer 的類,該類包含一個私有變量 counter,其構造器非常簡單,只是將 counter 設置為 0.

緊接著是一個操作符重載,這意味著這個類的每個實例都是被當作一個簡單函數來調用的。一般我們調用類的某個方法時會這樣 object.fooMethod(),但現在你實際上是直接調用了對象,如object(). 因為我們是在操作符重載函數中將整個對象傳遞給了線程類。最后是一個 getCounter 方法,返回 counter 變量的值。

再下來是程序的入口函數 main(),我們創建了三個線程,不過只創建了一個 Incrementer 類的實例,然后將這個實例傳遞給三個線程,注意這里使用了 std::ref ,這相當于是傳遞了實例的引用對象,而不是對象的拷貝。

現在讓我們來看看程序執行的結果,如果這位傻叉程序員還夠聰明的話,他會使用 GCC 4.7 或者更新版本,或者是 Clang 3.1 來進行編譯,編譯方法:

 

 
  1. g++ -std=c++11 -lpthread -o threading_example main.cpp 

運行結果:

 

 
  1. [lucas@lucas-desktop src]$ ./threading_example 
  2. 218141 
  3. [lucas@lucas-desktop src]$ ./threading_example 
  4. 208079 
  5. [lucas@lucas-desktop src]$ ./threading_example 
  6. 100000 
  7. [lucas@lucas-desktop src]$ ./threading_example 
  8. 202426 
  9. [lucas@lucas-desktop src]$ ./threading_example 
  10. 172209 

但等等,不對啊,程序并沒有數數到30萬,有一次居然只數到10萬,為什么會這樣呢?好吧,加1操作對應實際的處理器指令其實包括:

 

 
  1. movl counter(%rip), %eax 
  2. addl $1, %eax 
  3. movl %eax, counter(%rip) 

首個指令將裝載 counter 的值到 %eax 寄存器,緊接著寄存器的值增1,然后將寄存器的值移給內存中 counter 所在的地址。

我聽到你在嘀咕:這不錯,可為什么會導致數數錯誤的問題呢?嗯,還記得我們以前說過線程會共享處理器,因為只有單核。因此在某些點上,一個線程會依照指令執行完成,但在很多情況下,操作系統會對線程說:時間結束了,到后面排隊再來,然后另外一個線程開始執行,當下一個線程開始執行時,它會從被暫停的那個位置開始執行。所以你猜會發生什么事,當前線程正準備執行寄存器加1操作時,系統把處理器交給另外一個線程?

我真的不知道會發生什么事,可能我們在準備加1時,另外一個線程進來了,重新將 counter 值加載到寄存器等多種情況的產生。誰也不知道到底發生了什么。

正確的做法

解決方案就是要求同一個時間內只允許一個線程訪問共享變量。這個可通過 std::mutex 類來解決。當線程進入時,加鎖、執行操作,然后釋放鎖。其他線程想要訪問這個共享資源必須等待鎖釋放。

互斥(mutex) 是操作系統確保鎖和解鎖操作是不可分割的。這意味著線程在對互斥量進行鎖和解鎖的操作是不會被中斷的。當線程對互斥量進行鎖或者解鎖時,該操作會在操作系統切換線程前完成。

而最好的事情是,當你試圖對互斥量進行加鎖操作時,其他的線程已經鎖住了該互斥量,那你就必須等待直到其釋放。操作系統會跟蹤哪個線程正在等待哪個互斥量,被堵塞的線程會進入 "blocked onm" 狀態,意味著操作系統不會給這個堵塞的線程任何處理器時間,直到互斥量解鎖,因此也不會浪費 CPU 的循環。如果有多個線程處于等待狀態,哪個線程最先獲得資源取決于操作系統本身,一般像 Windows 和 Linux 系統使用的是 FIFO 策略,在實時操作系統中則是基于優先級的。

現在讓我們對上面的代碼進行改進:

 

 
  1. #include <iostream> 
  2. #include <string> 
  3. #include <thread> 
  4. #include <vector> 
  5. #include <mutex> 
  6.  
  7. using std::thread; 
  8. using std::vector; 
  9. using std::cout; 
  10. using std::endl; 
  11. using std::mutex; 
  12.  
  13. class Incrementer 
  14. private
  15. int counter; 
  16. mutex m; 
  17.  
  18. public
  19. Incrementer() : counter{0} { }; 
  20.  
  21. void operator()() 
  22. for(int i = 0; i < 100000; i++) 
  23. this->m.lock(); 
  24. this->counter++; 
  25. this->m.unlock(); 
  26.  
  27. int getCounter() const 
  28. return this->counter; 
  29. }  
  30. }; 
  31.  
  32. int main() 
  33. // Create the threads which will each do some counting 
  34. vector<thread> threads; 
  35.  
  36. Incrementer counter; 
  37.  
  38. threads.push_back(thread(std::ref(counter))); 
  39. threads.push_back(thread(std::ref(counter))); 
  40. threads.push_back(thread(std::ref(counter))); 
  41.  
  42. for(auto &t : threads) 
  43. t.join(); 
  44.  
  45. cout << counter.getCounter() << endl; 
  46.  
  47. return 0; 

注意代碼上的變化:我們引入了 mutex 頭文件,增加了一個 m 的成員,類型是 mutex,在operator()() 中我們鎖住互斥量 m 然后對 counter 進行加1操作,然后釋放互斥量。

再次執行上述程序,結果如下:

 

 
  1. [lucas@lucas-desktop src]$ ./threading_example 
  2. 300000 
  3. [lucas@lucas-desktop src]$ ./threading_example 
  4. 300000 

這下數對了。不過在計算機科學中,沒有免費的午餐,使用互斥量會降低程序的性能,但這總比一個錯誤的程序要強吧。

防范異常

當對變量進行加1操作時,是可能會發生異常的,當然在我們這個例子中發生異常的機會微乎其微,但是在一些復雜系統中是極有可能的。上面的代碼并不是異常安全的,當異常發生時,程序已經結束了,可是互斥量還是處于鎖的狀態。

為了確?;コ饬吭诋惓0l生的情況下也能被解鎖,我們需要使用如下代碼:

 

 
  1. for(int i = 0; i < 100000; i++) 
  2. this->m.lock(); 
  3. try 
  4. this->counter++; 
  5. this->m.unlock(); 
  6. catch(...) 
  7. this->m.unlock(); 
  8. throw

但是,這代碼太多了,而只是為了對互斥量進行加鎖和解鎖。沒關系,我知道你很懶,因此推薦個更簡單的單行代碼解決方法,就是使用 std::lock_guard 類。這個類在創建時就鎖定了 mutex 對象,然后在結束時釋放。

繼續修改代碼:

 

 
  1. void operator()() 
  2. for(int i = 0; i < 100000; i++) 
  3. lock_guard<mutex> lock(this->m); 
  4.  
  5. // The lock has been created now, and immediatly locks the mutex 
  6. this->counter++; 
  7.  
  8. // This is the end of the for-loop scope, and the lock will be 
  9. // destroyed, and in the destructor of the lock, it will 
  10. // unlock the mutex 

上面代碼已然是異常安全了,因為當異常發生時,將會調用 lock 對象的析構函數,然后自動進行互斥量的解鎖。

記住,請使用放下代碼模板來編寫:

 

 
  1. void long_function() 
  2. // some long code 
  3.  
  4. // Just a pair of curly braces 
  5. // Temp scope, create lock 
  6. lock_guard<mutex> lock(this->m); 
  7.  
  8. // do some stuff 
  9.  
  10. // Close the scope, so the guard will unlock the mutex 

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩大陆毛片av| 国产成人精品在线视频| 久久久久久久久电影| 久久天天躁夜夜躁狠狠躁2022| 欧美怡春院一区二区三区| 日韩成人小视频| 成人中文字幕+乱码+中文字幕| 97色伦亚洲国产| 美日韩精品免费视频| 久久久久久久999精品视频| 成人黄色av免费在线观看| 亚洲国产天堂久久国产91| 精品二区三区线观看| 国内精品久久久久久久久| 国产精品第三页| 精品视频在线导航| 国产亚洲精品成人av久久ww| 国产精品美女久久久久久免费| 色与欲影视天天看综合网| 中文字幕不卡在线视频极品| 国产日韩欧美视频在线| 国产精品久久久久久久久久三级| 日韩视频欧美视频| 亚洲精品国产精品自产a区红杏吧| 日韩免费精品视频| 最近中文字幕mv在线一区二区三区四区| 中文精品99久久国产香蕉| 亚洲国产日韩欧美在线图片| 91香蕉国产在线观看| 国产精品爽爽爽| 国产成人激情小视频| 91地址最新发布| 国产精品男人的天堂| 日韩免费观看在线观看| 欧美野外wwwxxx| 91精品国产91久久| 亚洲综合日韩中文字幕v在线| 精品久久久中文| 国产精品入口免费视频一| 亚洲成年网站在线观看| 国产精品久久久精品| 国产在线999| 久久视频国产精品免费视频在线| 国产精品极品尤物在线观看| 7777精品视频| 亚洲精品视频网上网址在线观看| 国产精品久久一区| 在线激情影院一区| 国产专区精品视频| 色久欧美在线视频观看| 成人国内精品久久久久一区| 福利视频一区二区| 91精品国产91| 91精品国产91久久久久| 欧美视频免费在线观看| 精品国产999| 久久国产精品99国产精| 欧美中在线观看| 亚洲xxx自由成熟| 在线成人中文字幕| 97国产suv精品一区二区62| 亚洲精品狠狠操| 中文字幕亚洲综合久久| 成人免费视频xnxx.com| 欧美诱惑福利视频| 精品久久久久久久久久久久| 91av在线免费观看视频| 欧美高清在线观看| 国产一区二区日韩| 国产精品久久激情| 久久福利视频网| 亚洲电影免费观看高清完整版在线观看| 精品久久香蕉国产线看观看gif| 久久香蕉国产线看观看av| 6080yy精品一区二区三区| 91精品国产综合久久男男| 欧美日韩成人网| 欧美精品日韩www.p站| 91久久精品国产| 亚洲91av视频| 亚洲夜晚福利在线观看| 草民午夜欧美限制a级福利片| 在线播放国产一区二区三区| 久久深夜福利免费观看| 亚洲成年网站在线观看| 精品久久久久国产| 91chinesevideo永久地址| 少妇高潮久久77777| 久久手机精品视频| 日韩av网址在线| 欧美午夜片欧美片在线观看| 国产中文日韩欧美| 91中文字幕在线| 国产精品久久综合av爱欲tv| 日本精品一区二区三区在线播放视频| 欧美日韩国产麻豆| 亚洲影院高清在线| 成人久久精品视频| 国产精品亚洲网站| 国产精品高潮视频| 欧美高清视频一区二区| 欧美午夜xxx| 成人网在线免费看| 亚洲片国产一区一级在线观看| 午夜精品视频在线| 日本国产欧美一区二区三区| 久久99国产综合精品女同| 精品国产91乱高清在线观看| 国产成人精彩在线视频九色| 国产日韩中文字幕| 97视频在线观看网址| 亚洲sss综合天堂久久| 国产精品久久久久久久久久| 全亚洲最色的网站在线观看| 国产精品天天狠天天看| 亚洲级视频在线观看免费1级| 久久精品一本久久99精品| 91精品国产91久久久久福利| 欧美一区深夜视频| 午夜欧美不卡精品aaaaa| 久久免费少妇高潮久久精品99| 在线视频欧美性高潮| 亚洲天堂免费在线| 成人美女av在线直播| 美女久久久久久久久久久| 国产91网红主播在线观看| 黄色91在线观看| 日本一本a高清免费不卡| 成人美女免费网站视频| 国产女人18毛片水18精品| 欧美床上激情在线观看| 成人妇女免费播放久久久| 91最新国产视频| 揄拍成人国产精品视频| 欧美日韩一区二区在线播放| 亚洲成年人在线| 国产精品久久久久久久久久三级| 亚洲国产高清高潮精品美女| 国产精品免费观看在线| 亚洲精品乱码久久久久久按摩观| 91成人在线播放| 欧美国产高跟鞋裸体秀xxxhd| 国产精品色午夜在线观看| 91九色单男在线观看| 欧美激情在线狂野欧美精品| 亚洲国产99精品国自产| 亚洲精品在线91| 欧美性69xxxx肥| 91成人在线观看国产| 欧美孕妇性xx| 久久久久久国产精品三级玉女聊斋| 久久久日本电影| 久久久久久网站| 在线观看日韩www视频免费| 国产精品白嫩美女在线观看| 国产精品国模在线| 久久久久国产精品免费网站| 久久伊人精品一区二区三区| 欧美专区第一页| 亚洲国产精品yw在线观看| 亚洲精品国精品久久99热一| 永久免费毛片在线播放不卡| 国产免费一区二区三区在线能观看| 欧洲中文字幕国产精品|