無鎖執行緒通訊(2):例程與分析
阿新 • • 發佈:2018-12-27
首先,我們來看一個例子。
1 // 互動通道“沒有貨”狀態 2 #define COMMUNICATIONCHANNEL_STATE_THINGSNOTEXISTS 0 3 // 互動通道“有貨”狀態 4 #define COMMUNICATIONCHANNEL_STATE_THINGSEXISTS 1 5 // 互動通道結構 6 struct CommunicationChannel7 {
8 // 有貨沒貨的狀態 9 volatileint iState;
10 // 貨物:值0 11 int iValue0;
12 // 貨物:值1 13 int iValue1;
15 // 執行緒引數 16 struct ThreadParam
17 {
18 // 輸出通道 19 CommunicationChannel * pOutputChannel;
20 // 輸入通道 21 CommunicationChannel * pInputChannel;
22 // 減的值,用於製造奇數和偶數 23 int iSubValue;
24 // 值0的和 25 int iSum0;
26 // 值1的和 27 int iSum1;
28 };
29 // 執行緒處理函式 30 DWORD WINAPI ThreadProc( LPVOID lpParam )
32 // 取得引數 33 ThreadParam * pParam = (ThreadParam*)lpParam;
34 int iCounter =0;
35 bool bCountFinish =false;
36 bool bCalcFinish =false;
37 38 // 執行緒迴圈 39 while( true )
40 {
41 // 向外輸出數值給另一個執行緒 42 if( !bCountFinish )
43 {
44 // 輸出通道是否是無貨狀態 45 if( pParam->pOutputChannel->
46 {
47 // 狀態滿足,輸出數字 48 if( iCounter <10 )
49 {
50 // 頭10次,輸出一個序列 51 ++iCounter;
52 pParam->pOutputChannel->iValue0 = iCounter;
53 pParam->pOutputChannel->iValue1 = iCounter *2- pParam->iSubValue;
54 }
55 else 56 {
57 // 第11次,輸出0,不在進行向外輸出數字 58 pParam->pOutputChannel->iValue0 =0;
59 pParam->pOutputChannel->iValue1 =0;
60 bCountFinish =true;
61 }
62 // 修改輸出通道的狀態為有貨 63 pParam->pOutputChannel->iState = COMMUNICATIONCHANNEL_STATE_THINGSEXISTS;
64 }
65 }
66 // 根據另一個執行緒輸入的數值進行計算 67 if( !bCalcFinish )
68 {
69 // 檢查輸入通道是否有貨 70 if( pParam->pInputChannel->iState == COMMUNICATIONCHANNEL_STATE_THINGSEXISTS )
71 {
72 // 狀態滿足 73 if( pParam->pInputChannel->iValue0 !=0 )
74 {
75 // 輸入不是0,就累加到和上 76 pParam->iSum0 += pParam->pInputChannel->iValue0;
77 pParam->iSum1 += pParam->pInputChannel->iValue1;
78 // 修改輸入通道為無貨 79 pParam->pInputChannel->iState = COMMUNICATIONCHANNEL_STATE_THINGSNOTEXISTS;
80 }
81 else 82 {
83 // 否則,結束掉計算輸入數值
84 // 因為另一個輸出0就表示不再有新貨到達 85 bCalcFinish =true;
86 }
87 }
88 }
89 // 輸出和計算過程都結束,就跳出執行緒迴圈 90 if( bCountFinish && 91 bCalcFinish )
92 break;
93 }
94 95 return0;
96 }
97 98 int _tmain(int argc, _TCHAR* argv[])
99 {
100 // 初始化兩個互動通道,用於A到B的資訊傳送和B到A的資訊傳送。101 struct CommunicationChannel A2BChannel = { COMMUNICATIONCHANNEL_STATE_THINGSNOTEXISTS, 0, 0 };
102 struct CommunicationChannel B2AChannel = { COMMUNICATIONCHANNEL_STATE_THINGSNOTEXISTS, 0, 0 };
103 // 初始化兩個執行緒引數,用於執行緒A和執行緒B104 struct ThreadParam ThreadAParam = {
105 &A2BChannel,
106 &B2AChannel,
107 0,
108 0,
109 0110 };
111 struct ThreadParam ThreadBParam = {
112 &B2AChannel,
113 &A2BChannel,
114 1,
115 0,
116 0117 };
118 119 // 建立執行緒A,B,並等待他們結束。120 DWORD dwId =0;
121 HANDLE hThreadA = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, &ThreadAParam, 0, &dwId );
122 HANDLE hThreadB = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, &ThreadBParam, 0, &dwId );
123 WaitForSingleObject( hThreadA, INFINITE );
124 WaitForSingleObject( hThreadB, INFINITE );
125 // 輸出執行緒A,B的計算結果。126 printf( "Thread A sum0 = %d sum1 = %d\n", ThreadAParam.iSum0, ThreadAParam.iSum1 );
127 printf( "Thread B sum0 = %d sum1 = %d\n", ThreadBParam.iSum0, ThreadBParam.iSum1 );
128 129 // 計算並輸出正確的結果,用於比較這個方法的計算結果是否正確。130 int iCorrectSum0 =0;
131 int iCorrectSum1A =0;
132 int iCorrectSum1B =0;
133 for( int i =1;i <=10; ++i )
134 {
135 iCorrectSum0 += i;
136 iCorrectSum1A += i *2-1;
137 iCorrectSum1B += i *2;
138 }
139 printf( "Correct sum0 = %d sum1A = %d sum1B = %d\n", iCorrectSum0, iCorrectSum1A, iCorrectSum1B );
140 141 return0;
142 }
143 144
這個例子,使用第一篇裡面的方法,實現了兩個執行緒雙向通訊。
執行緒處理函式裡面進行的事情很簡單,就是:輸出通道沒貨,就放貨進去,輸入通道有貨,就取貨計算。
在這個過程裡,我們將執行緒A的輸出和執行緒B的計算結合起來看,稱為一個任務。
這個任務,它是如下圖所示的過程進行處理的。
紅色部分,是任務的主要處理部分。綠色的部分,則是隔離兩個執行緒中處理部分的重要因素。
從圖上看,如果填充資料和使用資料的紅色部分在垂直方向有交錯,那麼,就會導致執行緒同步問題。
但是要那樣,需要狀態滿足要求在設定狀態的前面才行。上帝說,那是不可能的,不設定狀態,怎麼可能達成狀態滿足要求。除非執行緒B穿越了。
所以,使用這種方法,紅色部分永遠不會重合,也就實現了無鎖的執行緒通訊!當然,只是在兩個執行緒間實現了。
從單個通道來看,這種方法可以形象的看作一個執行緒在不斷的喂資料,另一個執行緒則在不斷的吃資料。這裡就簡單的稱為“餵食”。
“餵食”是一種可用的方法,不過它也有缺點,比如得等另一個執行緒吃完才能喂新的食物;或者另一個執行緒得等第一個執行緒去喂才有東西吃。
接下來,得挑戰點高難度的:突破餵食,以及兩個執行緒的限制。