首先,我們來看一個例子。
1 // 交互通道“沒有貨”狀態(tài)
2 #define COMMUNICATIONCHANNEL_STATE_THINGSNOTEXISTS 0
3 // 交互通道“有貨”狀態(tài)
4 #define COMMUNICATIONCHANNEL_STATE_THINGSEXISTS 1
5 // 交互通道結(jié)構(gòu)
6 struct CommunicationChannel
7 {
8 // 有貨沒貨的狀態(tài)
9 volatile int iState;
10 // 貨物:值0
11 int iValue0;
12 // 貨物:值1
13 int iValue1;
14 };
15 // 線程參數(shù)
16 struct ThreadParam
17 {
18 // 輸出通道
19 CommunicationChannel * pOutputChannel;
20 // 輸入通道
21 CommunicationChannel * pInputChannel;
22 // 減的值,用于制造奇數(shù)和偶數(shù)
23 int iSubValue;
24 // 值0的和
25 int iSum0;
26 // 值1的和
27 int iSum1;
28 };
29 // 線程處理函數(shù)
30 DWORD WINAPI ThreadProc( LPVOID lpParam )
31 {
32 // 取得參數(shù)
33 ThreadParam * pParam = (ThreadParam*)lpParam;
34 int iCounter = 0;
35 bool bCountFinish = false;
36 bool bCalcFinish = false;
37
38 // 線程循環(huán)
39 while( true )
40 {
41 // 向外輸出數(shù)值給另一個線程
42 if( !bCountFinish )
43 {
44 // 輸出通道是否是無貨狀態(tài)
45 if( pParam->pOutputChannel->iState == COMMUNICATIONCHANNEL_STATE_THINGSNOTEXISTS )
46 {
47 // 狀態(tài)滿足,輸出數(shù)字
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,不在進行向外輸出數(shù)字
58 pParam->pOutputChannel->iValue0 = 0;
59 pParam->pOutputChannel->iValue1 = 0;
60 bCountFinish = true;
61 }
62 // 修改輸出通道的狀態(tài)為有貨
63 pParam->pOutputChannel->iState = COMMUNICATIONCHANNEL_STATE_THINGSEXISTS;
64 }
65 }
66 // 根據(jù)另一個線程輸入的數(shù)值進行計算
67 if( !bCalcFinish )
68 {
69 // 檢查輸入通道是否有貨
70 if( pParam->pInputChannel->iState == COMMUNICATIONCHANNEL_STATE_THINGSEXISTS )
71 {
72 // 狀態(tài)滿足
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 // 否則,結(jié)束掉計算輸入數(shù)值
84 // 因為另一個輸出0就表示不再有新貨到達
85 bCalcFinish = true;
86 }
87 }
88 }
89 // 輸出和計算過程都結(jié)束,就跳出線程循環(huán)
90 if( bCountFinish &&
91 bCalcFinish )
92 break;
93 }
94
95 return 0;
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 // 初始化兩個線程參數(shù),用于線程A和線程B
104 struct ThreadParam ThreadAParam = {
105 &A2BChannel,
106 &B2AChannel,
107 0,
108 0,
109 0
110 };
111 struct ThreadParam ThreadBParam = {
112 &B2AChannel,
113 &A2BChannel,
114 1,
115 0,
116 0
117 };
118
119 // 創(chuàng)建線程A,B,并等待他們結(jié)束。
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的計算結(jié)果。
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 // 計算并輸出正確的結(jié)果,用于比較這個方法的計算結(jié)果是否正確。
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 return 0;
142 }
143
144
這個例子,使用第一篇里面的方法,實現(xiàn)了兩個線程雙向通信。
線程處理函數(shù)里面進行的事情很簡單,就是:輸出通道沒貨,就放貨進去,輸入通道有貨,就取貨計算。
在這個過程里,我們將線程A的輸出和線程B的計算結(jié)合起來看,稱為一個
任務(wù)。
這個任務(wù),它是如下圖所示的過程進行處理的。

紅色部分,是任務(wù)的主要處理部分。綠色的部分,則是隔離兩個線程中處理部分的重要因素。
從圖上看,如果填充數(shù)據(jù)和使用數(shù)據(jù)的紅色部分在垂直方向有交錯,那么,就會導(dǎo)致線程同步問題。
但是要那樣,需要狀態(tài)滿足要求在設(shè)置狀態(tài)的前面才行。上帝說,那是不可能的,不設(shè)置狀態(tài),怎么可能達成狀態(tài)滿足要求。除非線程B穿越了。
所以,使用這種方法,紅色部分永遠不會重合,也就實現(xiàn)了無鎖的線程通信!當然,只是在兩個線程間實現(xiàn)了。
從單個通道來看,這種方法可以形象的看作一個線程在不斷的喂數(shù)據(jù),另一個線程則在不斷的吃數(shù)據(jù)。這里就簡單的稱為“
喂食”。
“
喂食”是一種可用的方法,不過它也有缺點,比如得等另一個線程吃完才能喂新的食物;或者另一個線程得等第一個線程去喂才有東西吃。
接下來,得挑戰(zhàn)點高難度的:突破喂食,以及兩個線程的限制。