秒殺多線程第三篇 原子操作 Interlocked系列函數

上一篇《多線程第一次親密接觸 CreateThread_beginthreadex本質區別》中講到一個多線程報數功能。為了描述方便和代碼簡潔起見,我們可以只輸出最后的報數結果來觀察程序是否運行出錯。這也非常類似于統計一個網站每天有多少用戶登錄,每個用戶登錄用一個線程模擬,線程運行時會將一個表示計數的變量遞增。程序在最后輸出計數的值表示有今天多少個用戶登錄,如果這個值不等于我們啟動的線程個數,那顯然說明這個程序是有問題的。整個程序代碼如下:

[cpp] view plaincopy
  1. #include?<stdio.h>??
  2. #include?<process.h>??
  3. #include?<windows.h>??
  4. volatile?long?g_nLoginCount;?//登錄次數??
  5. unsigned?int?__stdcall?Fun(void?*pPM);?//線程函數??
  6. const?int?THREAD_NUM?=?10;?//啟動線程數??
  7. unsigned?int?__stdcall?ThreadFun(void?*pPM)??
  8. {??
  9. ????Sleep(100);?//some?work?should?to?do??
  10. ????g_nLoginCount++;??
  11. ????Sleep(50);???
  12. ????return?0;??
  13. }??
  14. int?main()??
  15. {??
  16. ????g_nLoginCount?=?0;??
  17. ??
  18. ????HANDLE??handle[THREAD_NUM];??
  19. ????for?(int?i?=?0;?i?<?THREAD_NUM;?i++)??
  20. ????????handle[i]?=?(HANDLE)_beginthreadex(NULL,?0,?ThreadFun,?NULL,?0,?NULL);??
  21. ??????
  22. ????WaitForMultipleObjects(THREAD_NUM,?handle,?TRUE,?INFINITE);???
  23. ????printf("有%d個用戶登錄后記錄結果是%d\n",?THREAD_NUM,?g_nLoginCount);??
  24. ????return?0;??
  25. }??

程序中模擬的是10個用戶登錄,程序將輸出結果:

和上一篇的線程報數程序一樣,程序輸出的結果好象并沒什么問題。下面我們增加點用戶來試試,現在模擬50個用戶登錄,為了便于觀察結果,在程序中將50個用戶登錄過程重復20次,代碼如下:

[cpp] view plaincopy
  1. #include?<stdio.h>??
  2. #include?<windows.h>??
  3. volatile?long?g_nLoginCount;?//登錄次數??
  4. unsigned?int?__stdcall?Fun(void?*pPM);?//線程函數??
  5. const?DWORD?THREAD_NUM?=?50;//啟動線程數??
  6. DWORD?WINAPI?ThreadFun(void?*pPM)??
  7. {??
  8. ????Sleep(100);?//some?work?should?to?do??
  9. ????g_nLoginCount++;??
  10. ????Sleep(50);??
  11. ????return?0;??
  12. }??
  13. int?main()??
  14. {??
  15. ????printf("?????原子操作?Interlocked系列函數的使用\n");??
  16. ????printf("?--?by?MoreWindows(?http://blog.csdn.net/MoreWindows?)?--\n\n");??
  17. ??????
  18. ????//重復20次以便觀察多線程訪問同一資源時導致的沖突??
  19. ????int?num=?20;??
  20. ????while?(num--)??
  21. ????{?????
  22. ????????g_nLoginCount?=?0;??
  23. ????????int?i;??
  24. ????????HANDLE??handle[THREAD_NUM];??
  25. ????????for?(i?=?0;?i?<?THREAD_NUM;?i++)??
  26. ????????????handle[i]?=?CreateThread(NULL,?0,?ThreadFun,?NULL,?0,?NULL);??
  27. ????????WaitForMultipleObjects(THREAD_NUM,?handle,?TRUE,?INFINITE);??
  28. ????????printf("有%d個用戶登錄后記錄結果是%d\n",?THREAD_NUM,?g_nLoginCount);??
  29. ????}??
  30. ????return?0;??
  31. }??

運行結果如下圖:

現在結果水落石出,明明有50個線程執行了g_nLoginCount++;操作,但結果輸出是不確定的,有可能為50,但也有可能小于50

?????? 要解決這個問題,我們就分析下g_nLoginCount++;操作。在VC6.0編譯器對g_nLoginCount++;這一語句打個斷點,再按F5進入調試狀態,然后按下Debug工具欄的Disassembly按鈕,這樣就出現了匯編代碼窗口。可以發現在C/C++語言中一條簡單的自增語句其實是由三條匯編代碼組成的,如下圖所示。

講解下這三條匯編意思:

第一條匯編將g_nLoginCount的值從內存中讀取到寄存器eax中。

第二條匯編將寄存器eax中的值與1相加,計算結果仍存入寄存器eax中。

第三條匯編將寄存器eax中的值寫回內存中。

?????? 這樣由于線程執行的并發性,很可能線程A執行到第二句時,線程B開始執行,線程B將原來的值又寫入寄存器eax中,這樣線程A所主要計算的值就被線程B修改了。這樣執行下來,結果是不可預知的——可能會出現50,可能小于50

?????? 因此在多線程環境中對一個變量進行讀寫時,我們需要有一種方法能夠保證對一個值的遞增操作是原子操作——即不可打斷性,一個線程在執行原子操作時,其它線程必須等待它完成之后才能開始執行該原子操作。這種涉及到硬件的操作會不會很復雜了,幸運的是,Windows系統為我們提供了一些以Interlocked開頭的函數來完成這一任務(下文將這些函數稱為Interlocked系列函數)。

下面列出一些常用的Interlocked系列函數:

1.增減操作

LONG__cdeclInterlockedIncrement(LONG volatile* Addend);

LONG__cdeclInterlockedDecrement(LONG volatile* Addend);

返回變量執行增減操作之后的值

LONG__cdec InterlockedExchangeAdd(LONG volatile* Addend, LONGValue);

返回運算后的值,注意!加個負數就是減。

?

2.賦值操作

LONG__cdeclInterlockedExchange(LONG volatile* Target, LONGValue);

Value就是新值,函數會返回原先的值。

?

在本例中只要使用InterlockedIncrement()函數就可以了。將線程函數代碼改成:

[cpp] view plaincopy
  1. DWORD?WINAPI?ThreadFun(void?*pPM)??
  2. {??
  3. ????Sleep(100);//some?work?should?to?do??
  4. ????//g_nLoginCount++;??
  5. ????InterlockedIncrement((LPLONG)&g_nLoginCount);??
  6. ????Sleep(50);??
  7. ????return?0;??
  8. }??

再次運行,可以發現結果會是唯一的。

?????? 因此,在多線程環境下,我們對變量的自增自減這些簡單的語句也要慎重思考,防止多個線程導致的數據訪問出錯。更多介紹,請訪問MSDNSynchronization Functions這一章節,地址為 http://msdn.microsoft.com/zh-cn/library/aa909196.aspx

?

看到這里,相信本系列首篇《秒殺多線程第一篇 多線程筆試面試題匯總》中選擇題第一題(百度筆試題)應該可以秒殺掉了吧(知其然也知其所以然),正確答案是D。另外給個附加問題,程序中是用50個線程模擬用戶登錄,有興趣的同學可以試下用100個線程來模擬一下(上機試試絕對會有意外發現^_^)。

?

下一篇《秒殺多線程第四篇 一個經典多線程同步問題》將提出一個稍為復雜點但卻非常經典的多線程同步互斥問題,這個問題會采用不同的方法來解答,從而讓你充分熟練多線程同步互斥的“招式”。更多精彩,歡迎繼續參閱。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/254858.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/254858.shtml
英文地址,請注明出處:http://en.pswp.cn/news/254858.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Vue 教程第九篇—— 動畫和過度效果

過渡效果 SPA 中組件的切換有一種生硬的隱藏顯示感覺&#xff0c;為了更好的用戶體驗&#xff0c;讓組件切換時淡出淡入&#xff0c;Vue 提供了專門的組件 transition。 過濾效果應用場景 條件渲染 (使用 v-if)條件展示 (使用 v-show)動態組件組件根節點過渡狀態 enter&#xf…

halcon create_ocr_class_svm 使用SVM分類器創建OCR分類器

目錄create_ocr_class_svm&#xff08;算子&#xff09;描述參數create_ocr_class_svm&#xff08;算子&#xff09; create_ocr_class_svm - 使用支持隨機向量機制創建OCR分類器。 create_ocr_class_svm&#xff08;:: WidthCharacter&#xff0c;HeightCharacter&#xff0…

碼率跟視頻質量有關系

碼率跟視頻質量有關系.首先要清楚, 相同的視頻編碼方式下, 碼率越高肯定畫面越清晰. 但是高到一定值, 再往上的畫面改善程度就不明顯了, 只會增大文件體積. 所以碼率選的合適, 才可以保證清晰度又保持文件不會太大. 個人經驗如果是h.264編碼(當前最好的視頻壓縮編碼方案), …

SQL 字符串分割表函數

1 --字符串分割表函數2 declare str varchar(1000)3 declare split varchar(10) 4 5 declare i int;6 declare count int;7 8 declare ChildStr varchar(1000);9 declare splitStr varchar(1000); 10 declare Index int; 11 12 declare table as table (rowId int,splitStr va…

語句:分支語句、switch case ——7月22日

語句的類型包括&#xff1a;聲明語句、表達式語句、選擇語句、循環語句、跳轉語句、異常語句 1&#xff0e;聲明語句引&#xff1a;入新的變量或常量。 變量聲明可以選擇為變量賦值。 在常量聲明中必須賦值。 例如&#xff1a; int i 0;//聲明變量i 并賦值&#xff0c;也可以不…

halcon write_ocr_trainf 將訓練字符存儲到文件中

目錄write_ocr_trainf&#xff08;運算符&#xff09;描述參數write_ocr_trainf&#xff08;運算符&#xff09; write_ocr_trainf - 將訓練字符存儲到文件中。 write_ocr_trainf&#xff08;Character&#xff0c;Image :: Class&#xff0c;TrainingFile ? 描述 運算符w…

碼率計算文章

http://bbs.dvbcn.com/showtopic-41431-1.html

PostgreSQL Oracle 兼容性之 - INDEX SKIP SCAN (遞歸查詢變態優化) 非驅動列索引掃描優化...

標簽 PostgreSQL , Oracle , index skip scan , 非驅動列條件 , 遞歸查詢 , 子樹 背景 對于輸入條件在復合索引中為非驅動列的&#xff0c;如何高效的利用索引掃描&#xff1f; 在Oracle中可以使用index skip scan來實現這類CASE的高效掃描&#xff1a; INDEX跳躍掃描一般用在W…

如何確定鏡頭CCD靶面尺寸?

在組建機器視覺系統時&#xff0c;需要選用適合實際應用的產品。今天&#xff0c;中國機器視覺商城的培訓課堂為您帶來的是關于工業鏡頭CCD靶面尺寸的確定方法。 在選擇鏡頭時&#xff0c;我們通常要注意一個原則&#xff1a;即小尺寸靶面的CCD可使用對應規格更大的鏡頭&#x…

lua去掉字符串中的UTF-8的BOM三個字節

廢話不多說&#xff0c;還是先說點吧&#xff0c;項目中lua讀取的text文件如果有BOM&#xff0c;客戶端解析就會報錯&#xff0c;所以我看了看&#xff0c;任務編輯器swGameTaskEditor 在寫入文件的時候&#xff0c;也不知道為什么有的文件就是UTF-8BOM格式&#xff1b;但一般都…

JQuery對象與DOM對象的區別與轉換

1.jQuery對象和DOM對象的區別 DOM對象&#xff0c;即是我們用傳統的方法(javascript)獲得的對象&#xff0c;jQuery對象即是用jQuery類庫的選擇器獲得的對象; eg: var domObj document.getElementById("id"); //DOM對象var $obj $("#id"); //jQuery對象;…

halcon append_ocr_trainf 將字符添加到訓練文件中

目錄append_ocr_trainf&#xff08;算子&#xff09;描述參數append_ocr_trainf&#xff08;算子&#xff09; append_ocr_trainf - 將字符添加到訓練文件中。 append_ocr_trainf&#xff08;Character&#xff0c;Image :: Class&#xff0c;TrainingFile ? 描述 運算符a…

CCD 尺寸

CCD&#xff08;包括CMOS感光元件&#xff09;的面積是按其矩形對角線英寸長度為指標的。這和定義電視屏幕尺寸類似。一英寸是25.4毫米。1/2.0英寸、1/1.8都是指CCD 對角線有多少分之一英寸長&#xff0c;分母小的其分數值就大&#xff0c;相應感光元件面積也大。 1/2.…

Quagga的安裝碰到的問題

1.如果出現以下錯誤&#xff1a; vtysh: symbol lookup error: /usr/local/lib/libreadline.so.6: undefined symbol: UP 解決方法如下: 1.rootlocalhost:~ # cd /usr/local/lib 2.rootlocalhost:/usr/local/lib# ls -la libreadline* 3.rootlocalhost:/usr/local/lib# mkd…

X264電影壓縮率畫質

X264電影壓縮率畫質全對比&#xff1a; http://www.mov8.com/dvd/freetalk_show.asp?id29778

halcon read_ocr_trainf 從文件中讀取訓練字符并轉換為圖像

目錄read_ocr_trainf&#xff08;算子&#xff09;描述參數read_ocr_trainf&#xff08;算子&#xff09; read_ocr_trainf - 從文件中讀取訓練字符并轉換為圖像。 read_ocr_trainf&#xff08;&#xff1a;Characters&#xff1a;TrainingFile&#xff1a;CharacterNames&am…

(十二)洞悉linux下的Netfilteramp;iptables:iptables命令行工具源碼解析【下】

iptables用戶空間和內核空間的交互 iptables目前已經支持IPv4和IPv6兩個版本了&#xff0c;因此它在實現上也需要同時兼容這兩個版本。iptables-1.4.0在這方面做了很好的設計&#xff0c;主要是由libiptc庫來實現。libiptc是iptables control library的簡稱&#xff0c;是Netfi…

Linux 下實現普通用戶只能寫入某個目錄

今天老婆問了我一個問題&#xff1a;如何在linux 下實現某個目錄普通用戶能夠寫入文件&#xff0c;但是不能刪除或修改&#xff08;只能由root 刪除或修改&#xff09;。開始的兩分鐘里&#xff0c;我初步判斷這是做不到的&#xff0c;因為linux 下能 寫入&#xff08;w&#x…

CCD和CMOS攝像頭成像原理以及其他區別

&#xfeff;&#xfeff;CCD的第二層是分色濾色片&#xff0c;目前有兩種分色方式&#xff0c;一是RGB原色分色法&#xff0c;另一個則是CMYG補色分色法&#xff0c;這兩種方法各有利弊。不過以產量來看&#xff0c;原色和補色CCD的比例大約在2&#xff1a;1左右。原色CCD的優…

FFMPEG分析比較細的文章

http://blog.csdn.net/ym012/article/details/6538301