基礎數據類型多線程是否需要加鎖


對於多線程訪問同一變量是否需要加鎖的問題,先前大家都討論過。今天用代碼驗證了一下之前的猜想:32位CPU與內存的最小交換數據為4字節/次,這也是結構體要對齊4字節的原因。在物理上,CPU對於同一4字節的內存單元,不可能寫2個字節的同時,又讀了3字節。

測試環境為:

XEON 2CPU*2
Windows7

采用50,50,50線程交叉讀寫,試驗代碼如下:

C/C++ code
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<br data-filtered= "filtered" >
int  g_test;<br data-filtered= "filtered" >
int  temp;<br data-filtered= "filtered" >
BOOL  g_bRunning;<br data-filtered= "filtered" >
DWORD  WINAPI thWriteProc1( LPVOID  lParam)<br data-filtered= "filtered" >
{<br data-filtered= "filtered" >
     while (g_bRunning)<br data-filtered= "filtered" >
     {<br data-filtered= "filtered" >
         g_test = 12345678;<br data-filtered= "filtered" >
         Sleep(1);<br data-filtered= "filtered" >
     }<br data-filtered= "filtered" >
     return  0;<br data-filtered= "filtered" >
}<br data-filtered= "filtered" >
DWORD  WINAPI thWriteProc2( LPVOID  lParam)<br data-filtered= "filtered" >
{<br data-filtered= "filtered" >
     while (g_bRunning)<br data-filtered= "filtered" >
     {<br data-filtered= "filtered" >
         g_test = 13579246;<br data-filtered= "filtered" >
         Sleep(1);<br data-filtered= "filtered" >
     }<br data-filtered= "filtered" >
     return  0;<br data-filtered= "filtered" >
}<br data-filtered= "filtered" >
<br data-filtered= "filtered" >
DWORD  WINAPI thReadProc( LPVOID  lParam)<br data-filtered= "filtered" >
{<br data-filtered= "filtered" >
     while (g_bRunning)<br data-filtered= "filtered" >
     {<br data-filtered= "filtered" >
         temp = g_test; //讀取值<br data-filtered="filtered">
         if  ( temp != 12345678 && temp != 13579246 )<br data-filtered= "filtered" >
         {<br data-filtered= "filtered" >
             g_bRunning = FALSE;<br data-filtered= "filtered" >
             CString str;<br data-filtered= "filtered" >
             str.Format( "read error!%d" , temp);<br data-filtered= "filtered" >
             AfxMessageBox(str);<br data-filtered= "filtered" >
             break ;<br data-filtered= "filtered" >
         }<br data-filtered= "filtered" >
         Sleep(1);<br data-filtered= "filtered" >
     }<br data-filtered= "filtered" >
     return  0;<br data-filtered= "filtered" >
}<br data-filtered= "filtered" >
void  CTestMultiyAccessIntDlg::OnButton1() <br data-filtered= "filtered" >
{<br data-filtered= "filtered" >
     g_bRunning = TRUE;<br data-filtered= "filtered" >
     for  int  i = 0; i < 50; i++ )<br data-filtered= "filtered" >
     {<br data-filtered= "filtered" >
         //創建50個寫線程1<br data-filtered="filtered">
         CreateThread( NULL, 0, thWriteProc1, NULL, 0, NULL );<br data-filtered= "filtered" >
     }<br data-filtered= "filtered" >
     for  int  i = 0; i < 50; i++ )<br data-filtered= "filtered" >
     {<br data-filtered= "filtered" >
         //創建50個寫線程2<br data-filtered="filtered">
         CreateThread( NULL, 0, thWriteProc2, NULL, 0, NULL );<br data-filtered= "filtered" >
     }<br data-filtered= "filtered" >
     for  int  i = 0; i < 50; i++ )<br data-filtered= "filtered" >
     {<br data-filtered= "filtered" >
         //創建50個讀線程<br data-filtered="filtered">
         CreateThread( NULL, 0, thReadProc, NULL, 0, NULL );<br data-filtered= "filtered" >
     }<br data-filtered= "filtered" >
}<br data-filtered= "filtered" >



測試方法:
改變g_test的類型,給g_test賦予不同的值(不要超過類型的上限值)

測試現象:
當g_test為int,short char時,不存在多線程交叉讀寫錯誤的問題
當g_test為double, float, __int64時,存在多線程交叉讀寫錯誤的問題,對於__int64,當賦值小於0xFFFFFFFF時不出錯,當大於0xFFFFFFFF時出錯
當g_test為CString時,存在交叉讀寫錯誤,有時候程序崩潰
另:不加Sleep(1)機器卡死過,CPU占用率達到100%,4個核心占用率全滿,可以保證運行在多核環境下

現象分析:
(1)int short char均為小於4字節的連續內存塊,CPU一條指令就可以讀寫它們的值,CPU不可能同一個時間執行兩條指令
(2)double為8字節,如果寫線程先寫了4字節,讀線程讀了8字節,這自然導致數據被破壞
(3)float也為4字節,我也不是太清楚為什么不行,可能是VC對浮點數的處理比較特殊有關,浮點數具有復雜內存結構
(4)__int64為8字節,存在和(2)相同的情況,如果__int64小於等於0xFFFFFFFF,相當於只改變了低4字節,因此就沒有問題
(5)CString為類類型,具有復雜結構,顯然不行

結論:
1.對於int,short,char,BOOL等小於等於4字節的簡單數據類型,如果無邏輯上的先后關系,多線程讀寫可以完全不用加鎖
2.盡管float為4字節,多線程訪問時也需要加鎖
3.對於大於4字節的簡單類型,比如double,__int64等,多線程讀寫必須加鎖。
4.對於所有復雜類型,比如類,結構體,容器等類型必須加鎖

盡管對int等類型的多線程讀寫不需要加鎖,但是邏輯上有必要加鎖的還是應該加鎖
例如:對於一個多線程訪問的全局變量int g_test
int count = g_test/1024;
int mod = g_test%1024;
由於是兩條語句,執行完第一條之后,別的線程很可能已經修改了g_test的值,如果希望這兩條語句執行時,g_test不發生變化,就必須加鎖,以保證兩條語句執行的整體性。
Lock();
int count = g_test/1024;
int mod= g_test%1024;
UnLock();
如果不加鎖,也可以改為先保存到一個臨時變量里
int temp = g_test;
int count = temp/1024;
int mod = temp%1024;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM