用戶方式中線程的同步
1. 僅一條語句用不用考慮線程同步的問題?
當(dāng)使用高級語言編程時,我們往往會認為一條語句是最小的原子訪問,CPU不會在這條語句中間運行其他的線程。這是錯誤的,因為即使非常簡單的一條高級語言的語句,經(jīng)編譯器編譯后也可能變成多行代碼由計算機來執(zhí)行。因此必須考慮線程同步的問題。任何線程都不應(yīng)該通過調(diào)用簡單的C語句來修改共享的變量。
2. 互鎖函數(shù)有那些?
(1) LONG InterlockedExchangeAdd ( LPLONG Addend, LONG Increment );
Addend為長整型變量的地址,Increment為想要在Addend指向的長整型變量上增加的數(shù)值(可以是負數(shù))。這個函數(shù)的主要作用是保證這個加操作為一個原子訪問。
(2) LONG InterlockedExchange( LPLONG Target, LONG Value );
用第二個參數(shù)的值取代第一個參數(shù)指向的值。函數(shù)返回值為原始值。
(3) PVOID InterlockedExchangePointer( PVOID *Target, PVOID Value );
用第二個參數(shù)的值取代第一個參數(shù)指向的值。函數(shù)返回值為原始值。
(4) LONG InterlockedCompareExchange(
LPLONG Destination, LONG Exchange, LONG Comperand );
如果第三個參數(shù)與第一個參數(shù)指向的值相同,那么用第二個參數(shù)取代第一個參數(shù)指向的值。函數(shù)返回值為原始值。
(5) PVOID InterlockedCompareExchangePointer (
PVOID *Destination, PVOID Exchange, PVOID Comperand );
如果第三個參數(shù)與第一個參數(shù)指向的值相同,那么用第二個參數(shù)取代第一個參數(shù)指向的值。函數(shù)返回值為原始值。
3. 為什么單CPU的計算機不應(yīng)該使用循環(huán)鎖?
舉例說明:
BOOL g_bResourceUse = FALSE;
……
void ThreadFunc1( )
{
BOOL bResourceUse = FALSE;
while( 1 )
{
bResourceUse = InterlockedExchange( &g_bResourceUse, TRUE );
if( bResourceUse == FALSE )
{
break;
}
Sleep( 0 );
}
……
……
……
InterlockedExchange( &g_bResourceUse, FALSE );
}
首先循環(huán)鎖會浪費CPU時間。CPU必須不斷地比較兩個值,直到一個值由于另一個線程而“奇妙地”改變?yōu)橹埂6沂褂迷撗h(huán)鎖的線程都應(yīng)該為同一優(yōu)先級,并且應(yīng)當(dāng)使用SetProcessPriorityBoost函數(shù)或SetThreadPriorityBoost函數(shù)禁止線程優(yōu)先級的動態(tài)提高功能,否則優(yōu)先級較低的線程可能永遠不能被調(diào)用。
4. 如何使用volatile聲明變量?
如果是對共享資源的地址進行使用如&g_Resource那么可以不使用volatile,因為將一個變量地址傳遞給一個函數(shù)時,該函數(shù)必須從內(nèi)存讀取該值。優(yōu)化程序不會對它產(chǎn)生任何影響。如果直接使用變量,必須有一個volatile類型的限定詞。它告訴編譯器,變量可以被應(yīng)用程序本身以外的某個東西進行修改,這些東西包括操作系統(tǒng),硬件或同時執(zhí)行的線程等。volatile限定詞會告訴編譯器,不要對該變量進行任何優(yōu)化,并且總是重新加載來自該變量的內(nèi)存單元的值。否則編譯器會把變量的值存入CPU寄存器,每次對寄存器進行操作。線程就會進入一個無限循環(huán),永遠無法喚醒。
5. 如何使用關(guān)鍵代碼段實現(xiàn)線程的同步?
如果需要一小段代碼以原子操作的方式執(zhí)行,這時簡單的互鎖函數(shù)已不能滿足需要,必須使用關(guān)鍵代碼段來解決問題。不過使用關(guān)鍵代碼段時,很容易陷入死鎖狀態(tài),因為在等待進入關(guān)鍵代碼段時無法設(shè)定超時值。關(guān)鍵代碼段是通過對共享資源設(shè)置一個標(biāo)志來實現(xiàn)的,就像廁所門上的“有人/沒人”標(biāo)志一樣。這個標(biāo)志就是一個CRITICAL_SECTION變量。該變量在任何一個線程使用它之前應(yīng)當(dāng)進行初始化。初始化可以有兩種方法,使用InitializeCriticalSection函數(shù)和InitializeCriticalSectionAndSpinCount函數(shù)。然后在每個使用共享資源的線程函數(shù)的關(guān)鍵代碼段前使用EnterCriticalSection函數(shù)或者使用TryEnterCriticalSection函數(shù)。在關(guān)鍵代碼段使用之后調(diào)用LeaveCriticalSection函數(shù)。在所有的線程都不再使用該共享資源后應(yīng)當(dāng)調(diào)用DeleteCriticalSection函數(shù)來清除該標(biāo)志。舉例說明:
const int MAX_TIMES = 1000;
int g_intIndex = 0;
DWORD g_dwTimes[MAX_TIMES];
CRITICAL_SECTION g_cs;
void Init( )
{
……
InitializeCriticalSection( &g_cs );
……
}
DWORD WINAPI FirstThread( PVOID lpParam )
{
while ( g_intIndex < MAX_TIMES )
{
EnterCriticalSection( &g_cs );
g_dwTimes[g_intIndex] = GetTickCount( );
g_intIndex++;
LeaveCriticalSection( &g_cs );
}
return 0;
}
DWORD WINAPI SecondThread( PVOID lpParam )
{
while ( g_intIndex < MAX_TIMES )
{
EnterCriticalSection( &g_cs );
g_intIndex++;
g_dwTimes[g_intIndex - 1] = GetTickCount( );
LeaveCriticalSection( &g_cs );
}
return 0;
}
void Close( )
{
……
DeleteCriticalSection( &g_cs );
……
}
使用關(guān)鍵代碼段應(yīng)當(dāng)注意一些技巧:
(1) 每個共享資源使用一個CRITICAL_SECTION變量。
這樣在當(dāng)前線程占有一個資源時,另一個資源可以被其他線程占有。
EnterCriticalSection( &g_cs );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_intArray[intLoop] = 0;
g_uintArray[intLoop] = 0;
}
LeaveCriticalSection( &g_cs );
改為:
EnterCriticalSection( &g_csInt );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_intArray[intLoop] = 0;
}
LeaveCriticalSection( &g_csInt );
EnterCriticalSection( &g_csUint );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_uintArray[intLoop] = 0;
}
LeaveCriticalSection( &g_csUint );
(2) 同時訪問多個資源,必須始終按照完全相同的順序請求對資源的訪問。
這樣才能避免死鎖狀態(tài)產(chǎn)生。離開的順序沒有關(guān)系。
Thread1:
EnterCriticalSection( &g_csInt );
EnterCriticalSection( &g_csUint );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_uintArray[intLoop] = g_intArray[intLoop];
}
LeaveCriticalSection( &g_csInt );
LeaveCriticalSection( &g_csUint );
Thread2:
EnterCriticalSection( &g_csUint );
EnterCriticalSection( &g_csInt );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_uintArray[intLoop] = g_intArray[intLoop];
}
LeaveCriticalSection( &g_csInt );
LeaveCriticalSection( &g_csUint );
改為:
Thread1:
EnterCriticalSection( &g_csInt );
EnterCriticalSection( &g_csUint );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_uintArray[intLoop] = g_intArray[intLoop];
}
LeaveCriticalSection( &g_csInt );
LeaveCriticalSection( &g_csUint );
Thread2:
EnterCriticalSection( &g_csInt );
EnterCriticalSection( &g_csUint );
for ( intLoop = 0; intLoop < 100; intLoop++ )
{
g_uintArray[intLoop] = g_intArray[intLoop];
}
LeaveCriticalSection( &g_csInt );
LeaveCriticalSection( &g_csUint );
(3) 不要長時間運行關(guān)鍵代碼段。
EnterCriticalSection( &g_cs );
SendMessage( hWnd, WM_SOMEMSG, &g_s, 0 );
LeaveCriticalSection( &g_cs );
改為:
EnterCriticalSection( &g_cs );
sTemp = g_s;
LeaveCriticalSection( &g_cs );
SendMessage( hWnd, WM_SOMEMSG, &sTemp, 0 );
6. InitializeCriticalSection/InitializeCriticalSectionAndSpinCount差別?
InitializeCriticalSection函數(shù)的返回值為空并且不會創(chuàng)建事件內(nèi)核對象,比較節(jié)省系統(tǒng)資源,但是一旦發(fā)生兩個或多個線程爭用關(guān)鍵代碼段的情況,如果內(nèi)存不足,關(guān)鍵代碼段可能被爭用,同時系統(tǒng)可能無法創(chuàng)建必要的事件內(nèi)核對象。這時EnterCriticalSection函數(shù)將會產(chǎn)生一個EXCEPTION_INVALID_HANDLE異常。這個錯誤非常少見。如果想對這種情況有所準(zhǔn)備,可以有兩種選擇??梢允褂媒Y(jié)構(gòu)化異常處理方法來跟蹤錯誤。當(dāng)錯誤發(fā)生時,既可以不訪問關(guān)鍵代碼段保護的資源,也可以等待某些內(nèi)存變成可用狀態(tài),然后再次調(diào)用EnterCriticalSection函數(shù)。
另一種選擇是使用InitializeCriticalSectionAndSpinCount,第二個參數(shù)dwSpinCount中,傳遞的是在使線程等待之前它試圖獲得資源時想要循環(huán)鎖循環(huán)迭代的次數(shù)。這個值可以是0至0x00FFFFFF之間的任何數(shù)字。如果在單處理器計算機上運行時調(diào)用該函數(shù),該參數(shù)被忽略,并且始終設(shè)置為0。使用InitializeCriticalSectionAndSpinCount函數(shù)創(chuàng)建關(guān)鍵代碼段,確保設(shè)置了dwSpinCount參數(shù)的高信息位。當(dāng)該函數(shù)發(fā)現(xiàn)高信息位已經(jīng)設(shè)置時,它就創(chuàng)建該事件內(nèi)核對象,并在初始化時將它與關(guān)鍵代碼段關(guān)聯(lián)起來。如果事件無法創(chuàng)建,該函數(shù)返回FALSE。可以更加妥善地處理代碼中的這個事件。如果事件創(chuàng)建成功,EnterCriticalSection將始終都能運行,并且決不會產(chǎn)生異常情況(如果總是預(yù)先分配事件內(nèi)核對象,就會浪費系統(tǒng)資源。只有當(dāng)代碼不能容許EnterCriticalSection運行失敗,或者有把握會出現(xiàn)爭用現(xiàn)象,或者預(yù)計進程將在內(nèi)存非常短缺的環(huán)境中運行時,才能預(yù)先分配事件內(nèi)核對象)。
7. TryEnterCriticalSection和EnterCriticalSection的差別是什么?
如果EnterCriticalSection將一個線程置于等待狀態(tài),那么該線程在很長時間內(nèi)就不能再次被調(diào)度。實際上,在編寫得不好的應(yīng)用程序中,該線程永遠不會再次被賦予CPU時間。TryEnterCriticalSection函數(shù)決不允許調(diào)用線程進入等待狀態(tài)。它的返回值能夠指明調(diào)用線程是否能夠獲得對資源的訪問權(quán)。TryEnterCriticalSection發(fā)現(xiàn)該資源已經(jīng)被另一個線程訪問,它就返回FALSE。在其他所有情況下,它均返回TRUE。運用這個函數(shù),線程能夠迅速查看它是否可以訪問某個共享資源,如果不能訪問,那么它可以繼續(xù)執(zhí)行某些其他操作,而不必進行等待。如果TryEnterCriticalSection函數(shù)確實返回了TRUE,那么CRITICAL_SECTION的成員變量已經(jīng)更新。Windows98沒有可以使用的TryEnterCriticalSection函數(shù)的實現(xiàn)代碼。
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請
點擊舉報。