1. 程式人生 > >無鎖執行緒通訊(2):例程與分析

無鎖執行緒通訊(2):例程與分析

 首先,我們來看一個例子。

  1 //    互動通道“沒有貨”狀態  2 #define COMMUNICATIONCHANNEL_STATE_THINGSNOTEXISTS 0  3 //    互動通道“有貨”狀態  4 #define COMMUNICATIONCHANNEL_STATE_THINGSEXISTS 1  5 //    互動通道結構  6 struct CommunicationChannel
  7 {
  8 //    有貨沒貨的狀態  9 volatileint iState;
 10 //    貨物:值0 11 int iValue0;
 12 //    貨物:值1 13 int iValue1;
 14 };
 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 )
 31 {
 32 //    取得引數 33     ThreadParam * pParam = (ThreadParam*)lpParam;
 34 int iCounter =0;
 35 bool bCountFinish =false;
 36 bool bCalcFinish =false;
 37  38 //    執行緒迴圈 39 whiletrue )
 40     {
 41 //    向外輸出數值給另一個執行緒 42 if!bCountFinish )
 43         {
 44 //    輸出通道是否是無貨狀態 45 if( pParam->pOutputChannel->
iState == COMMUNICATIONCHANNEL_STATE_THINGSNOTEXISTS )
 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, 00 };
102 struct CommunicationChannel B2AChannel = { COMMUNICATIONCHANNEL_STATE_THINGSNOTEXISTS, 00 };
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 forint 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穿越了。

所以,使用這種方法,紅色部分永遠不會重合,也就實現了無鎖的執行緒通訊!當然,只是在兩個執行緒間實現了。

從單個通道來看,這種方法可以形象的看作一個執行緒在不斷的喂資料,另一個執行緒則在不斷的吃資料。這裡就簡單的稱為“餵食”。

餵食”是一種可用的方法,不過它也有缺點,比如得等另一個執行緒吃完才能喂新的食物;或者另一個執行緒得等第一個執行緒去喂才有東西吃。

接下來,得挑戰點高難度的:突破餵食,以及兩個執行緒的限制。