執行緒的基礎知識(轉載)
執行緒的基礎知識(轉載)
2009-04-01 16:58:50| 分類: 專業! | 標籤: |字號大中小 訂閱
執行緒的基礎知識
1. 程式與執行緒有那些區別和聯絡?
每個程式至少需要一個執行緒。
程式由兩部分構成:程式核心物件,地址空間。執行緒也由兩部分組成:執行緒核心物件,作業系統用它來對執行緒實施管理。執行緒堆疊,用於維護執行緒在執行程式碼時需要的所有函式引數和區域性變數。
程式是不活潑的。程式從來不執行任何東西,它只是執行緒的容器。執行緒總是在某個程式環境中建立的,而且它的整個壽命期都在該程式中。
如果在單程式環境中,有多個執行緒正在執行,那麼這些執行緒將共享單個地址空間。這些執行緒能夠執行相同的程式碼,對相同的資料進行操作。這些執行緒還能共享核心物件控制程式碼,因為控制程式碼表依賴於每個程式而不是每個執行緒存在。
程式使用的系統資源比執行緒多得多。實際上,執行緒只有一個核心物件和一個堆疊,保留的記錄很少,因此需要很少的記憶體。因此始終都應該設法用增加執行緒來解決程式設計問題,避免建立新的程式。但是許多程式設計用多個程式來實現會更好些。
2. 如何使用_beginthreadex函式?
使用方法與CreateThread函式相同,只是呼叫引數型別需要轉換。
3. 如何使用CreateThread函式?
當CreateThread被呼叫時,系統建立一個執行緒核心物件。該執行緒核心物件不是執行緒本身,而是作業系統用來管理執行緒的較小的資料結構。使用時應當注意在不需要對執行緒核心進行訪問後呼叫CloseHandle函式關閉執行緒控制程式碼。因為CreateThread函式中使用某些C/C++執行期庫函式時會有記憶體洩漏,所以應當儘量避免使用。
引數含義:
lpThreadAttributes 如果傳遞NULL該執行緒使用預設安全屬性。如果希望所有的子程式能夠繼承該執行緒物件的控制程式碼,必須將它的bInheritHandle成員被初始化為TRUE。
dwStackSize 設定執行緒堆疊的地址空間。如果非0,函式將所有的儲存器保留並分配給執行緒的堆疊。如果是0,CreateThread就保留一個區域,並且將連結程式嵌入.exe檔案的/STACK連結程式開關資訊指明的儲存器容量分配給執行緒堆疊。
lpStartAddress 執行緒函式的地址。
lpParameter 傳遞給執行緒函式的引數。
dwCreationFlags 如果是0,執行緒建立後立即進行排程。如果是CREATE_SUSPENDED,系統對它進行初始化後暫停該執行緒的執行。
LpThreadId 用來存放系統分配給新執行緒的ID。
4. 如何終止執行緒的執行?
(1) 執行緒函式返回(最好使用這種方法)。
這是確保所有執行緒資源被正確地清除的唯一辦法。
如果執行緒能夠返回,就可以確保下列事項的實現:
線上程函式中建立的所有C++物件均將通過它們的撤消函式正確地撤消。
作業系統將正確地釋放執行緒堆疊使用的記憶體。
系統將執行緒的退出程式碼設定為執行緒函式的返回值。
系統將遞減執行緒核心物件的使用計數。
(2) 呼叫ExitThread函式(最好不要使用這種方法)。
該函式將終止執行緒的執行,並導致作業系統清除該執行緒使用的所有作業系統資源。但是,C++資源(如C++類物件)將不被撤消。
(3) 呼叫TerminateThread函式(應該避免使用這種方法)。
TerminateThread能撤消任何執行緒。執行緒的核心物件的使用計數也被遞減。 TerminateThread函式是非同步執行的函式。如果要確切地知道該執行緒已經終止執行,必須呼叫WaitForSingleObject或者類似的函式。當使用返回或呼叫ExitThread的方法撤消執行緒時,該執行緒的記憶體堆疊也被撤消。但是,如果使用TerminateThread,那麼在擁有執行緒的程式終止執行之前,系統不撤消該執行緒的堆疊。
(4) 包含執行緒的程式終止執行(應該避免使用這種方法)。
由於整個程式已經被關閉,程式使用的所有資源肯定已被清除。就像從每個剩餘的執行緒呼叫TerminateThread一樣。這意味著正確的應用程式清除沒有發生,即C++物件撤消函式沒有被呼叫,資料沒有轉至磁碟等等。
一旦執行緒不再執行,系統中就沒有別的執行緒能夠處理該執行緒的控制程式碼。然而別的執行緒可以調GetExitcodeThread來檢查由hThread標識的執行緒是否已經終止執行。如果它已經終止執行,則確定它的退出程式碼。
5. 為什麼不要使用_beginthread函式和_endthread函式?
與_beginthreadex函式相比引數少,限制多。無法建立暫停的執行緒,無法取得執行緒ID。_endthread函式無引數,執行緒退出程式碼必須為0。還有_endthread函式內部關閉了執行緒的控制程式碼,一旦退出將不能正確訪問執行緒控制程式碼。
6. 如何對程式或執行緒的核心進行引用?
HANDLE GetCurrentProcess( );
HANDLE GetCurrentThread( );
這兩個函式都能返回撥用執行緒的程式的偽控制程式碼或執行緒核心物件的偽控制程式碼。偽控制程式碼只能在當前的程式或執行緒中使用,在其它執行緒或程式將不能訪問。函式並不在建立程式的控制程式碼表中建立新控制程式碼。呼叫這些函式對程式或執行緒核心物件的使用計數沒有任何影響。如果呼叫CloseHandle,將偽控制程式碼作為引數來傳遞,那麼CloseHandle就會忽略該函式的呼叫並返回FALSE。
DWORD GetCurrentProcessId( );
DWORD GetCurrentThreadId( );
這兩個函式使得執行緒能夠查詢它的程式的唯一ID或它自己的唯一ID。
7. 如何將偽控制程式碼轉換為實控制程式碼?
HANDLE hProcessFalse = NULL;
HANDLE hProcessTrue = NULL;
HANDLE hThreadFalse = NULL;
HANDLE hThreadTrue = NULL;
hProcessFalse = GetCurrentProcess( );
hThreadFalse = GetCurrentThread( );
取得執行緒實控制程式碼:
DuplicateHandle( hProcessFalse, hThreadFalse, hProcessFalse, &hThreadTrue, 0, FALSE, DUPLICATE_SAME_ACCESS );
取得程式實控制程式碼:
DuplicateHandle( hProcessFalse, hProcessFalse, hProcessFalse, &hProcessTrue, 0, FALSE, DUPLICATE_SAME_ACCESS );
由於DuplicateHandle會遞增特定物件的使用計數,因此當完成對複製物件控制程式碼的使用時,應該將目標控制程式碼傳遞給CloseHandle,從而遞減物件的使用計數。
8. 在一個程式中可建立執行緒的最大數是得多少?
執行緒的最大數取決於該系統的可用虛擬記憶體的大小。預設每個執行緒最多可擁有至多1MB大小的棧的空間。所以,至多可建立2028個執行緒。如果減少預設堆疊的大小,則可以建立更多的執行緒。
執行緒的排程、優先順序和親緣性
1. 如何暫停和恢復執行緒的執行?
執行緒核心物件的內部有一個值指明執行緒的暫停計數。當呼叫CreateProcess或CreateThread函式時,就建立了執行緒的核心物件,並且它的暫停計數被初始化為1。因為執行緒的初始化需要時間,不能在系統做好充分的準備之前就開始執行執行緒。執行緒完全初始化好了之後,CreateProcess或 CreateThread要檢視是否已經傳遞了CREATE_SUSPENDED標誌。如果已經傳遞了這個標誌,那麼這些函式就返回,同時新執行緒處於暫停狀態。如果尚未傳遞該標誌,那麼該函式將執行緒的暫停計數遞減為0。當執行緒的暫停計數是0的時候,除非執行緒正在等待其他某種事情的發生,否則該執行緒就處於可排程狀態。在暫停狀態中建立一個執行緒,就能夠線上程有機會執行任何程式碼之前改變執行緒的執行環境(如優先順序)。一旦改變了執行緒的環境,必須使執行緒成為可排程執行緒。方法如下:
hThread = CreatThread( ……,CREATE_SUSPENDED,…… );
或
bCreate = CreatProcess( ……,CREATE_SUSPENDED,……,pProcInfo );
if( bCreate != FALSE )
{
hThread = pProcInfo.hThread;
}
……
……
……
ResumeThread( hThread );
CloseHandle( hThread );
ResumeThread成功,它將返回執行緒的前一個暫停計數,否則返回0xFFFFFFFF。
單個執行緒可以暫停若干次。如果一個執行緒暫停了3次,它必須恢復3次。建立執行緒時,除了使用 CREATE_SUSPENDED外,也可以呼叫SuspendThread函式來暫停執行緒的執行。任何執行緒都可以呼叫該函式來暫停另一個執行緒的執行(只要擁有執行緒的控制程式碼)。執行緒可以自行暫停執行,但是不能自行恢復執行。與ResumeThread一樣,SuspendThread返回的是執行緒的前一個暫停計數。執行緒暫停的最多次數可以是MAXIMUM_SUSPEND_COUNT次。SuspendThread與核心方式的執行是非同步進行的,但是線上程恢復執行之前,不會發生使用者方式的執行。呼叫SuspendThread時必須小心,因為不知道暫停執行緒執行時它在進行什麼操作。只有確切知道目標執行緒是什麼(或者目標執行緒正在做什麼),並且採取強有力的措施來避免因暫停執行緒的執行而帶來的問題或死鎖狀態,SuspendThread才是安全的。
2. 是否可以暫停和恢復程式的執行?
對於Windows來說,不存在暫停或恢復程式的概念,因為程式從來不會被安排獲得CPU時間。不過Windows確實允許一個程式暫停另一個程式中的所有執行緒的執行,但是從事暫停操作的程式必須是個除錯程式。特別是,程式必須呼叫WaitForDebugEvent和 ContinueDebugEvent之類的函式。由於競爭的原因,Windows沒有提供其他方法來暫停程式中所有執行緒的執行。
3. 如何使用sleep函式?
系統將在大約的指定毫秒數內使執行緒不可排程。Windows不是個實時作業系統。雖然執行緒可能在規定的時間被喚醒,但是它能否做到,取決於系統中還有什麼操作正在進行。
可以呼叫Sleep,並且為dwMilliseconds引數傳遞INFINITE。這將告訴系統永遠不要排程該執行緒。這不是一件值得去做的事情。最好是讓執行緒退出,並還原它的堆疊和核心物件。可以將0傳遞給Sleep。這將告訴系統,呼叫執行緒將釋放剩餘的時間片,並迫使系統排程另一個執行緒。但是,系統可以對剛剛呼叫Sleep的執行緒重新排程。如果不存在多個擁有相同優先順序的可排程執行緒,就會出現這種情況。
4. 如何轉換到另一個執行緒?
系統提供了SwitchToThread函式。當呼叫這個函式的時候,系統要檢視是否存在一個迫切需要CPU時間的執行緒。如果沒有執行緒迫切需要CPU時間,SwitchToThread就會立即返回。如果存在一個迫切需要CPU 時間的執行緒,SwitchToThread就對該執行緒進行排程(該執行緒的優先順序可能低於呼叫SwitchToThread的執行緒)。這個迫切需要CPU時間的執行緒可以執行一個時間段,然後系統排程程式照常執行。該函式允許一個需要資源的執行緒強制另一個優先順序較低、而目前卻擁有該資源的執行緒放棄該資源。如果呼叫SwitchToThread函式時沒有其他執行緒能夠執行,那麼該函式返回FALSE,否則返回一個非0值。呼叫SwitchToThread與呼叫 Sleep是相似的。差別是SwitchToThread允許優先順序較低的執行緒執行;而即使有低優先順序執行緒迫切需要CPU時間,Sleep也能夠立即對呼叫執行緒重新進行排程。
5. 如何取得執行緒執行的時間?
(1) 簡單取得執行緒大概執行時間:
DWORD dwStartTime = 0;
DWORD dwEndTime = 0;
DWORD dwRunTime = 0;
dwStartTime = GetTickCount( );
……
……
……
dwEndTime = GetTickCount( );
dwRunTime = dwEndTime – dwStartTime;
(2) 呼叫GetThreadTimes的函式:
引數含義:
hThread 執行緒控制程式碼
lpCreationTime 建立時間:英國格林威治時間
lpExitTime 退出時間:英國格林威治時間,如果執行緒仍然在執行,退出時間則未定義
lpKernelTime 核心時間:指明執行緒執行作業系統程式碼已經經過了多少個100ns的CPU時間
lpUserTime 使用者時間:指明執行緒執行應用程式程式碼已經經過了多少個100ns的CPU時間
GetProcessTimes是個類似GetThreadTimes的函式,適用於程式中的所有執行緒(甚至是已經終止執行的執行緒)。返回的核心時間是所有程式的執行緒在核心程式碼中經過的全部時間的總和。GetThreadTimes和 GetProcessTimes這兩個函式在Windows98中不起作用。在Windows98中,沒有一個可靠的機制可供應用程式來確定執行緒或程式已經使用了多少CPU時間。
6. 程式的優先順序類有哪些?
優先順序類 識別符號 描述
實時 REALTIME_PRIORITY_CLASS 立即對事件作出響應,執行關鍵時間的任務。會搶先於作業系統元件之前執行。
高 HIGH_PRIORITY_CLASS 立即對事件作出響應,執行關鍵時間的任務。
高於正常 ABOVE_NORMAL_PRIORITY_CLASS 在正常優先順序與高優先順序之間執行(Windows2000)。
正常 NORMAL_PRIORITY_CLASS 沒有特殊排程需求
低於正常 BELOW_NORMAL_PRIORITY_CLASS 在正常優先順序與空閒優先順序之間執行(Windows2000)。
空閒 IDLE_PRIORITY_CLASS 在系統空閒時執行。
設定方法:
BOOL SetPriorityClass( HANDLE hProcess, DWORD dwPriority );
DWORD GetPriorityClass( HANDLE hProcess );
使用命令外殼啟動一個程式時,該程式的起始優先順序是正常優先順序。如果使用Start命令來啟動該程式,可以使用一個開關來設定應用程式的起始優先順序。例如:
c:\>START /LOW CALC.EXE
Start命令還能識別/BELOWNORMAL、/NORMAL、/ABOVENORMAL、/HIGH和/REALTIME等開關。
7. 執行緒的相對優先順序有哪些?
相對優先順序 識別符號 描述
關鍵時間 THREAD_PRIORITY_TIME_CRITICAL 對於實時優先順序類執行緒在優先順序31上執行,對於其他優先順序類,執行緒在優先順序15上執行。
最高 THREAD_PRIORITY_HIGHEST 執行緒在高於正常優先順序上兩級上執行。
高於正常 THREAD_PRIORITY_ABOVE_NORMAL 執行緒在正常優先順序上一級上執行。
正常 THREAD_PRIORITY_NORMAL 執行緒在程式的優先順序類上正常執行。
低於正常 THREAD_PRIORITY_BELOW_NORMAL 執行緒在低於正常優先順序下一級上執行。
最低 THREAD_PRIORITY_LOWEST 執行緒在低於正常優先順序下兩級上執行。
空閒 THREAD_PRIORITY_IDLE 對於實時優先順序類執行緒在優先順序16上執行對於其他優先順序類執行緒在優先順序1上執行。
設定方法:
BOOL SetThreadPriority( HANDLE hThread, DWORD dwPriority );
DWORD GetThreadPriorityClass( HANDLE hThread );
8. 如何避免系統動態提高執行緒的優先順序等級?
系統常常要提高執行緒的優先順序等級,以便對視窗訊息或讀取磁碟等I/O事件作出響應。或者當系統發現一個執行緒在大約3至4s內一直渴望得到CPU時間,它就將這個渴望得到CPU時間的執行緒的優先順序動態提高到15,並讓該執行緒執行兩倍於它的時間量。當到了兩倍時間量的時候,該執行緒的優先順序立即返回到它的基本優先順序。下面的函式可以對系統的排程方式進行設定:
BOOL SetProcessPriorityBoost( HANDLE hProcess, BOOL bDisableBoost );
BOOL GetProcessPriorityBoost( HANDLE hProcess, PBOOL pbDisableBoost );
BOOL SetThreadPriorityBoost( HANDLE hThread, BOOL bDisableBoost );
BOOL GetThreadPriorityBoost( HANDLE hThread, PBOOL pbDisableBoost );
SetProcessPriorityBoost負責告訴系統啟用或停用進行中的所有執行緒的優先順序提高功能,而SetThreadPriorityBoost則啟用或停用各個執行緒的優先順序提高功能。Windows98沒有提供這4個函式的有用的實現程式碼。
使用者方式中執行緒的同步
1. 僅一條語句用不用考慮執行緒同步的問題?
當使用高階語言程式設計時,我們往往會認為一條語句是最小的原子訪問,CPU不會在這條語句中間執行其他的執行緒。這是錯誤的,因為即使非常簡單的一條高階語言的語句,經編譯器編譯後也可能變成多行程式碼由計算機來執行。因此必須考慮執行緒同步的問題。任何執行緒都不應該通過呼叫簡單的C語句來修改共享的變數。
2. 互鎖函式有那些?
(1) LONG InterlockedExchangeAdd ( LPLONG Addend, LONG Increment );
Addend為長整型變數的地址,Increment為想要在Addend指向的長整型變數上增加的數值(可以是負數)。這個函式的主要作用是保證這個加操作為一個原子訪問。
(2) LONG InterlockedExchange( LPLONG Target, LONG Value );
用第二個引數的值取代第一個引數指向的值。函式返回值為原始值。
(3) PVOID InterlockedExchangePointer( PVOID *Target, PVOID Value );
用第二個引數的值取代第一個引數指向的值。函式返回值為原始值。
(4) LONG InterlockedCompareExchange(
LPLONG Destination, LONG Exchange, LONG Comperand );
如果第三個引數與第一個引數指向的值相同,那麼用第二個引數取代第一個引數指向的值。函式返回值為原始值。
(5) PVOID InterlockedCompareExchangePointer (
PVOID *Destination, PVOID Exchange, PVOID Comperand );
如果第三個引數與第一個引數指向的值相同,那麼用第二個引數取代第一個引數指向的值。函式返回值為原始值。
3. 為什麼單CPU的計算機不應該使用迴圈鎖?
舉例說明:
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 );
}
首先迴圈鎖會浪費CPU時間。CPU必須不斷地比較兩個值,直到一個值由於另一個執行緒而“奇妙地”改變為止。而且使用該迴圈鎖的執行緒都應該為同一優先順序,並且應當使用SetProcessPriorityBoost函式或 SetThreadPriorityBoost函式禁止執行緒優先順序的動態提高功能,否則優先順序較低的執行緒可能永遠不能被呼叫。
4. 如何使用volatile宣告變數?
如果是對共享資源的地址進行使用如&g_Resource那麼可以不使用 volatile,因為將一個變數地址傳遞給一個函式時,該函式必須從記憶體讀取該值。優化程式不會對它產生任何影響。如果直接使用變數,必須有一個 volatile型別的限定詞。它告訴編譯器,變數可以被應用程式本身以外的某個東西進行修改,這些東西包括作業系統,硬體或同時執行的執行緒等。 volatile限定詞會告訴編譯器,不要對該變數進行任何優化,並且總是重新載入來自該變數的記憶體單元的值。否則編譯器會把變數的值存入CPU暫存器,每次對暫存器進行操作。執行緒就會進入一個無限迴圈,永遠無法喚醒。
5. 如何使用關鍵程式碼段實現執行緒的同步?
如果需要一小段程式碼以原子操作的方式執行,這時簡單的互鎖函式已不能滿足需要,必須使用關鍵程式碼段來解決問題。不過使用關鍵程式碼段時,很容易陷入死鎖狀態,因為在等待進入關鍵程式碼段時無法設定超時值。關鍵程式碼段是通過對共享資源設定一個標誌來實現的,就像廁所門上的“有人/沒人”標誌一樣。這個標誌就是一個CRITICAL_SECTION變數。該變數在任何一個執行緒使用它之前應當進行初始化。初始化可以有兩種方法,使用InitializeCriticalSection函式和 InitializeCriticalSectionAndSpinCount函式。然後在每個使用共享資源的執行緒函式的關鍵程式碼段前使用 EnterCriticalSection函式或者使用TryEnterCriticalSection函式。在關鍵程式碼段使用之後呼叫 LeaveCriticalSection函式。在所有的執行緒都不再使用該共享資源後應當呼叫DeleteCriticalSection函式來清除該標誌。舉例說明:
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 );
……
}
使用關鍵程式碼段應當注意一些技巧:
(1) 每個共享資源使用一個CRITICAL_SECTION變數。
這樣在當前執行緒佔有一個資源時,另一個資源可以被其他執行緒佔有。
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) 同時訪問多個資源,必須始終按照完全相同的順序請求對資源的訪問。
這樣才能避免死鎖狀態產生。離開的順序沒有關係。
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) 不要長時間執行關鍵程式碼段。
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函式的返回值為空並且不會建立事件核心物件,比較節省系統資源,但是一旦發生兩個或多個執行緒爭用關鍵程式碼段的情況,如果記憶體不足,關鍵程式碼段可能被爭用,同時系統可能無法建立必要的事件核心物件。這時EnterCriticalSection函式將會產生一個EXCEPTION_INVALID_HANDLE異常。這個錯誤非常少見。如果想對這種情況有所準備,可以有兩種選擇。可以使用結構化異常處理方法來跟蹤錯誤。當錯誤發生時,既可以不訪問關鍵程式碼段保護的資源,也可以等待某些記憶體變成可用狀態,然後再次呼叫 EnterCriticalSection函式。
另一種選擇是使用InitializeCriticalSectionAndSpinCount,第二個引數dwSpinCount中,傳遞的是在使執行緒等待之前它試圖獲得資源時想要迴圈鎖迴圈迭代的次數。這個值可以是0至0x00FFFFFF之間的任何數字。如果在單處理器計算機上執行時呼叫該函式,該引數被忽略,並且始終設定為0。使用InitializeCriticalSectionAndSpinCount函式建立關鍵程式碼段,確保設定了 dwSpinCount引數的高資訊位。當該函式發現高資訊位已經設定時,它就建立該事件核心物件,並在初始化時將它與關鍵程式碼段關聯起來。如果事件無法建立,該函式返回FALSE。可以更加妥善地處理程式碼中的這個事件。如果事件建立成功,EnterCriticalSection將始終都能執行,並且決不會產生異常情況(如果總是預先分配事件核心物件,就會浪費系統資源。只有當程式碼不能容許EnterCriticalSection執行失敗,或者有把握會出現爭用現象,或者預計程式將在記憶體非常短缺的環境中執行時,才能預先分配事件核心物件)。
7. TryEnterCriticalSection和EnterCriticalSection的差別是什麼?
如果EnterCriticalSection將一個執行緒置於等待狀態,那麼該執行緒在很長時間內就不能再次被排程。實際上,在編寫得不好的應用程式中,該執行緒永遠不會再次被賦予CPU時間。TryEnterCriticalSection函式決不允許呼叫執行緒進入等待狀態。它的返回值能夠指明呼叫執行緒是否能夠獲得對資源的訪問權。TryEnterCriticalSection發現該資源已經被另一個執行緒訪問,它就返回FALSE。在其他所有情況下,它均返回TRUE。運用這個函式,執行緒能夠迅速檢視它是否可以訪問某個共享資源,如果不能訪問,那麼它可以繼續執行某些其他操作,而不必進行等待。如果 TryEnterCriticalSection函式確實返回了TRUE,那麼CRITICAL_SECTION的成員變數已經更新。Windows98 沒有可以使用的TryEnterCriticalSection函式的實現程式碼。
原文:http://dev.csdn.net/article/50/50454.shtm
讀後:
關鍵是:WaitForSingleObject函式.
WaitForSingleObject的用法
WaitForSingleObject 的用法
DWORD
WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
引數 hHandle 是一個事件的控制程式碼,第二個引數 dwMilliseconds 是時間間隔。如果時間是有訊號狀態返回 WAIT_OBJECT_0 ,如果時間超過 dwMilliseconds 值但時間事件還是無訊號狀態則返回 WAIT_TIMEOUT 。
hHandle 可以是下列物件的控制程式碼:
Change notification
Console input
Event
Job
Memory resource notification
Mutex
Process
Semaphore
Thread
Waitable timer
WaitForSingleObject 函式用來檢測 hHandle 事件的訊號狀態,當函式的執行時間超過 dwMilliseconds 就返回,但如果引數 dwMilliseconds 為 INFINITE 時函式將直到相應時間事件變成有訊號狀態才返回,否則就一直等待下去,直到 WaitForSingleObject 有返回直才執行後面的程式碼
2009-04-01 16:58:50| 分類: 專業! | 標籤: |字號大中小 訂閱
執行緒的基礎知識
1. 程式與執行緒有那些區別和聯絡?
每個程式至少需要一個執行緒。
程式由兩部分構成:程式核心物件,地址空間。執行緒也由兩部分組成:執行緒核心物件,作業系統用它來對執行緒實施管理。執行緒堆疊,用於維護執行緒在執行程式碼時需要的所有函式引數和區域性變數。
程式是不活潑的。程式從來不執行任何東西,它只是執行緒的容器。執行緒總是在某個程式環境中建立的,而且它的整個壽命期都在該程式中。
如果在單程式環境中,有多個執行緒正在執行,那麼這些執行緒將共享單個地址空間。這些執行緒能夠執行相同的程式碼,對相同的資料進行操作。這些執行緒還能共享核心物件控制程式碼,因為控制程式碼表依賴於每個程式而不是每個執行緒存在。
程式使用的系統資源比執行緒多得多。實際上,執行緒只有一個核心物件和一個堆疊,保留的記錄很少,因此需要很少的記憶體。因此始終都應該設法用增加執行緒來解決程式設計問題,避免建立新的程式。但是許多程式設計用多個程式來實現會更好些。
2. 如何使用_beginthreadex函式?
使用方法與CreateThread函式相同,只是呼叫引數型別需要轉換。
3. 如何使用CreateThread函式?
當CreateThread被呼叫時,系統建立一個執行緒核心物件。該執行緒核心物件不是執行緒本身,而是作業系統用來管理執行緒的較小的資料結構。使用時應當注意在不需要對執行緒核心進行訪問後呼叫CloseHandle函式關閉執行緒控制程式碼。因為CreateThread函式中使用某些C/C++執行期庫函式時會有記憶體洩漏,所以應當儘量避免使用。
引數含義:
lpThreadAttributes 如果傳遞NULL該執行緒使用預設安全屬性。如果希望所有的子程式能夠繼承該執行緒物件的控制程式碼,必須將它的bInheritHandle成員被初始化為TRUE。
dwStackSize 設定執行緒堆疊的地址空間。如果非0,函式將所有的儲存器保留並分配給執行緒的堆疊。如果是0,CreateThread就保留一個區域,並且將連結程式嵌入.exe檔案的/STACK連結程式開關資訊指明的儲存器容量分配給執行緒堆疊。
lpStartAddress 執行緒函式的地址。
lpParameter 傳遞給執行緒函式的引數。
dwCreationFlags 如果是0,執行緒建立後立即進行排程。如果是CREATE_SUSPENDED,系統對它進行初始化後暫停該執行緒的執行。
LpThreadId 用來存放系統分配給新執行緒的ID。
4. 如何終止執行緒的執行?
(1) 執行緒函式返回(最好使用這種方法)。
這是確保所有執行緒資源被正確地清除的唯一辦法。
如果執行緒能夠返回,就可以確保下列事項的實現:
線上程函式中建立的所有C++物件均將通過它們的撤消函式正確地撤消。
作業系統將正確地釋放執行緒堆疊使用的記憶體。
系統將執行緒的退出程式碼設定為執行緒函式的返回值。
系統將遞減執行緒核心物件的使用計數。
(2) 呼叫ExitThread函式(最好不要使用這種方法)。
該函式將終止執行緒的執行,並導致作業系統清除該執行緒使用的所有作業系統資源。但是,C++資源(如C++類物件)將不被撤消。
(3) 呼叫TerminateThread函式(應該避免使用這種方法)。
TerminateThread能撤消任何執行緒。執行緒的核心物件的使用計數也被遞減。 TerminateThread函式是非同步執行的函式。如果要確切地知道該執行緒已經終止執行,必須呼叫WaitForSingleObject或者類似的函式。當使用返回或呼叫ExitThread的方法撤消執行緒時,該執行緒的記憶體堆疊也被撤消。但是,如果使用TerminateThread,那麼在擁有執行緒的程式終止執行之前,系統不撤消該執行緒的堆疊。
(4) 包含執行緒的程式終止執行(應該避免使用這種方法)。
由於整個程式已經被關閉,程式使用的所有資源肯定已被清除。就像從每個剩餘的執行緒呼叫TerminateThread一樣。這意味著正確的應用程式清除沒有發生,即C++物件撤消函式沒有被呼叫,資料沒有轉至磁碟等等。
一旦執行緒不再執行,系統中就沒有別的執行緒能夠處理該執行緒的控制程式碼。然而別的執行緒可以調GetExitcodeThread來檢查由hThread標識的執行緒是否已經終止執行。如果它已經終止執行,則確定它的退出程式碼。
5. 為什麼不要使用_beginthread函式和_endthread函式?
與_beginthreadex函式相比引數少,限制多。無法建立暫停的執行緒,無法取得執行緒ID。_endthread函式無引數,執行緒退出程式碼必須為0。還有_endthread函式內部關閉了執行緒的控制程式碼,一旦退出將不能正確訪問執行緒控制程式碼。
6. 如何對程式或執行緒的核心進行引用?
HANDLE GetCurrentProcess( );
HANDLE GetCurrentThread( );
這兩個函式都能返回撥用執行緒的程式的偽控制程式碼或執行緒核心物件的偽控制程式碼。偽控制程式碼只能在當前的程式或執行緒中使用,在其它執行緒或程式將不能訪問。函式並不在建立程式的控制程式碼表中建立新控制程式碼。呼叫這些函式對程式或執行緒核心物件的使用計數沒有任何影響。如果呼叫CloseHandle,將偽控制程式碼作為引數來傳遞,那麼CloseHandle就會忽略該函式的呼叫並返回FALSE。
DWORD GetCurrentProcessId( );
DWORD GetCurrentThreadId( );
這兩個函式使得執行緒能夠查詢它的程式的唯一ID或它自己的唯一ID。
7. 如何將偽控制程式碼轉換為實控制程式碼?
HANDLE hProcessFalse = NULL;
HANDLE hProcessTrue = NULL;
HANDLE hThreadFalse = NULL;
HANDLE hThreadTrue = NULL;
hProcessFalse = GetCurrentProcess( );
hThreadFalse = GetCurrentThread( );
取得執行緒實控制程式碼:
DuplicateHandle( hProcessFalse, hThreadFalse, hProcessFalse, &hThreadTrue, 0, FALSE, DUPLICATE_SAME_ACCESS );
取得程式實控制程式碼:
DuplicateHandle( hProcessFalse, hProcessFalse, hProcessFalse, &hProcessTrue, 0, FALSE, DUPLICATE_SAME_ACCESS );
由於DuplicateHandle會遞增特定物件的使用計數,因此當完成對複製物件控制程式碼的使用時,應該將目標控制程式碼傳遞給CloseHandle,從而遞減物件的使用計數。
8. 在一個程式中可建立執行緒的最大數是得多少?
執行緒的最大數取決於該系統的可用虛擬記憶體的大小。預設每個執行緒最多可擁有至多1MB大小的棧的空間。所以,至多可建立2028個執行緒。如果減少預設堆疊的大小,則可以建立更多的執行緒。
執行緒的排程、優先順序和親緣性
1. 如何暫停和恢復執行緒的執行?
執行緒核心物件的內部有一個值指明執行緒的暫停計數。當呼叫CreateProcess或CreateThread函式時,就建立了執行緒的核心物件,並且它的暫停計數被初始化為1。因為執行緒的初始化需要時間,不能在系統做好充分的準備之前就開始執行執行緒。執行緒完全初始化好了之後,CreateProcess或 CreateThread要檢視是否已經傳遞了CREATE_SUSPENDED標誌。如果已經傳遞了這個標誌,那麼這些函式就返回,同時新執行緒處於暫停狀態。如果尚未傳遞該標誌,那麼該函式將執行緒的暫停計數遞減為0。當執行緒的暫停計數是0的時候,除非執行緒正在等待其他某種事情的發生,否則該執行緒就處於可排程狀態。在暫停狀態中建立一個執行緒,就能夠線上程有機會執行任何程式碼之前改變執行緒的執行環境(如優先順序)。一旦改變了執行緒的環境,必須使執行緒成為可排程執行緒。方法如下:
hThread = CreatThread( ……,CREATE_SUSPENDED,…… );
或
bCreate = CreatProcess( ……,CREATE_SUSPENDED,……,pProcInfo );
if( bCreate != FALSE )
{
hThread = pProcInfo.hThread;
}
……
……
……
ResumeThread( hThread );
CloseHandle( hThread );
ResumeThread成功,它將返回執行緒的前一個暫停計數,否則返回0xFFFFFFFF。
單個執行緒可以暫停若干次。如果一個執行緒暫停了3次,它必須恢復3次。建立執行緒時,除了使用 CREATE_SUSPENDED外,也可以呼叫SuspendThread函式來暫停執行緒的執行。任何執行緒都可以呼叫該函式來暫停另一個執行緒的執行(只要擁有執行緒的控制程式碼)。執行緒可以自行暫停執行,但是不能自行恢復執行。與ResumeThread一樣,SuspendThread返回的是執行緒的前一個暫停計數。執行緒暫停的最多次數可以是MAXIMUM_SUSPEND_COUNT次。SuspendThread與核心方式的執行是非同步進行的,但是線上程恢復執行之前,不會發生使用者方式的執行。呼叫SuspendThread時必須小心,因為不知道暫停執行緒執行時它在進行什麼操作。只有確切知道目標執行緒是什麼(或者目標執行緒正在做什麼),並且採取強有力的措施來避免因暫停執行緒的執行而帶來的問題或死鎖狀態,SuspendThread才是安全的。
2. 是否可以暫停和恢復程式的執行?
對於Windows來說,不存在暫停或恢復程式的概念,因為程式從來不會被安排獲得CPU時間。不過Windows確實允許一個程式暫停另一個程式中的所有執行緒的執行,但是從事暫停操作的程式必須是個除錯程式。特別是,程式必須呼叫WaitForDebugEvent和 ContinueDebugEvent之類的函式。由於競爭的原因,Windows沒有提供其他方法來暫停程式中所有執行緒的執行。
3. 如何使用sleep函式?
系統將在大約的指定毫秒數內使執行緒不可排程。Windows不是個實時作業系統。雖然執行緒可能在規定的時間被喚醒,但是它能否做到,取決於系統中還有什麼操作正在進行。
可以呼叫Sleep,並且為dwMilliseconds引數傳遞INFINITE。這將告訴系統永遠不要排程該執行緒。這不是一件值得去做的事情。最好是讓執行緒退出,並還原它的堆疊和核心物件。可以將0傳遞給Sleep。這將告訴系統,呼叫執行緒將釋放剩餘的時間片,並迫使系統排程另一個執行緒。但是,系統可以對剛剛呼叫Sleep的執行緒重新排程。如果不存在多個擁有相同優先順序的可排程執行緒,就會出現這種情況。
4. 如何轉換到另一個執行緒?
系統提供了SwitchToThread函式。當呼叫這個函式的時候,系統要檢視是否存在一個迫切需要CPU時間的執行緒。如果沒有執行緒迫切需要CPU時間,SwitchToThread就會立即返回。如果存在一個迫切需要CPU 時間的執行緒,SwitchToThread就對該執行緒進行排程(該執行緒的優先順序可能低於呼叫SwitchToThread的執行緒)。這個迫切需要CPU時間的執行緒可以執行一個時間段,然後系統排程程式照常執行。該函式允許一個需要資源的執行緒強制另一個優先順序較低、而目前卻擁有該資源的執行緒放棄該資源。如果呼叫SwitchToThread函式時沒有其他執行緒能夠執行,那麼該函式返回FALSE,否則返回一個非0值。呼叫SwitchToThread與呼叫 Sleep是相似的。差別是SwitchToThread允許優先順序較低的執行緒執行;而即使有低優先順序執行緒迫切需要CPU時間,Sleep也能夠立即對呼叫執行緒重新進行排程。
5. 如何取得執行緒執行的時間?
(1) 簡單取得執行緒大概執行時間:
DWORD dwStartTime = 0;
DWORD dwEndTime = 0;
DWORD dwRunTime = 0;
dwStartTime = GetTickCount( );
……
……
……
dwEndTime = GetTickCount( );
dwRunTime = dwEndTime – dwStartTime;
(2) 呼叫GetThreadTimes的函式:
引數含義:
hThread 執行緒控制程式碼
lpCreationTime 建立時間:英國格林威治時間
lpExitTime 退出時間:英國格林威治時間,如果執行緒仍然在執行,退出時間則未定義
lpKernelTime 核心時間:指明執行緒執行作業系統程式碼已經經過了多少個100ns的CPU時間
lpUserTime 使用者時間:指明執行緒執行應用程式程式碼已經經過了多少個100ns的CPU時間
GetProcessTimes是個類似GetThreadTimes的函式,適用於程式中的所有執行緒(甚至是已經終止執行的執行緒)。返回的核心時間是所有程式的執行緒在核心程式碼中經過的全部時間的總和。GetThreadTimes和 GetProcessTimes這兩個函式在Windows98中不起作用。在Windows98中,沒有一個可靠的機制可供應用程式來確定執行緒或程式已經使用了多少CPU時間。
6. 程式的優先順序類有哪些?
優先順序類 識別符號 描述
實時 REALTIME_PRIORITY_CLASS 立即對事件作出響應,執行關鍵時間的任務。會搶先於作業系統元件之前執行。
高 HIGH_PRIORITY_CLASS 立即對事件作出響應,執行關鍵時間的任務。
高於正常 ABOVE_NORMAL_PRIORITY_CLASS 在正常優先順序與高優先順序之間執行(Windows2000)。
正常 NORMAL_PRIORITY_CLASS 沒有特殊排程需求
低於正常 BELOW_NORMAL_PRIORITY_CLASS 在正常優先順序與空閒優先順序之間執行(Windows2000)。
空閒 IDLE_PRIORITY_CLASS 在系統空閒時執行。
設定方法:
BOOL SetPriorityClass( HANDLE hProcess, DWORD dwPriority );
DWORD GetPriorityClass( HANDLE hProcess );
使用命令外殼啟動一個程式時,該程式的起始優先順序是正常優先順序。如果使用Start命令來啟動該程式,可以使用一個開關來設定應用程式的起始優先順序。例如:
c:\>START /LOW CALC.EXE
Start命令還能識別/BELOWNORMAL、/NORMAL、/ABOVENORMAL、/HIGH和/REALTIME等開關。
7. 執行緒的相對優先順序有哪些?
相對優先順序 識別符號 描述
關鍵時間 THREAD_PRIORITY_TIME_CRITICAL 對於實時優先順序類執行緒在優先順序31上執行,對於其他優先順序類,執行緒在優先順序15上執行。
最高 THREAD_PRIORITY_HIGHEST 執行緒在高於正常優先順序上兩級上執行。
高於正常 THREAD_PRIORITY_ABOVE_NORMAL 執行緒在正常優先順序上一級上執行。
正常 THREAD_PRIORITY_NORMAL 執行緒在程式的優先順序類上正常執行。
低於正常 THREAD_PRIORITY_BELOW_NORMAL 執行緒在低於正常優先順序下一級上執行。
最低 THREAD_PRIORITY_LOWEST 執行緒在低於正常優先順序下兩級上執行。
空閒 THREAD_PRIORITY_IDLE 對於實時優先順序類執行緒在優先順序16上執行對於其他優先順序類執行緒在優先順序1上執行。
設定方法:
BOOL SetThreadPriority( HANDLE hThread, DWORD dwPriority );
DWORD GetThreadPriorityClass( HANDLE hThread );
8. 如何避免系統動態提高執行緒的優先順序等級?
系統常常要提高執行緒的優先順序等級,以便對視窗訊息或讀取磁碟等I/O事件作出響應。或者當系統發現一個執行緒在大約3至4s內一直渴望得到CPU時間,它就將這個渴望得到CPU時間的執行緒的優先順序動態提高到15,並讓該執行緒執行兩倍於它的時間量。當到了兩倍時間量的時候,該執行緒的優先順序立即返回到它的基本優先順序。下面的函式可以對系統的排程方式進行設定:
BOOL SetProcessPriorityBoost( HANDLE hProcess, BOOL bDisableBoost );
BOOL GetProcessPriorityBoost( HANDLE hProcess, PBOOL pbDisableBoost );
BOOL SetThreadPriorityBoost( HANDLE hThread, BOOL bDisableBoost );
BOOL GetThreadPriorityBoost( HANDLE hThread, PBOOL pbDisableBoost );
SetProcessPriorityBoost負責告訴系統啟用或停用進行中的所有執行緒的優先順序提高功能,而SetThreadPriorityBoost則啟用或停用各個執行緒的優先順序提高功能。Windows98沒有提供這4個函式的有用的實現程式碼。
使用者方式中執行緒的同步
1. 僅一條語句用不用考慮執行緒同步的問題?
當使用高階語言程式設計時,我們往往會認為一條語句是最小的原子訪問,CPU不會在這條語句中間執行其他的執行緒。這是錯誤的,因為即使非常簡單的一條高階語言的語句,經編譯器編譯後也可能變成多行程式碼由計算機來執行。因此必須考慮執行緒同步的問題。任何執行緒都不應該通過呼叫簡單的C語句來修改共享的變數。
2. 互鎖函式有那些?
(1) LONG InterlockedExchangeAdd ( LPLONG Addend, LONG Increment );
Addend為長整型變數的地址,Increment為想要在Addend指向的長整型變數上增加的數值(可以是負數)。這個函式的主要作用是保證這個加操作為一個原子訪問。
(2) LONG InterlockedExchange( LPLONG Target, LONG Value );
用第二個引數的值取代第一個引數指向的值。函式返回值為原始值。
(3) PVOID InterlockedExchangePointer( PVOID *Target, PVOID Value );
用第二個引數的值取代第一個引數指向的值。函式返回值為原始值。
(4) LONG InterlockedCompareExchange(
LPLONG Destination, LONG Exchange, LONG Comperand );
如果第三個引數與第一個引數指向的值相同,那麼用第二個引數取代第一個引數指向的值。函式返回值為原始值。
(5) PVOID InterlockedCompareExchangePointer (
PVOID *Destination, PVOID Exchange, PVOID Comperand );
如果第三個引數與第一個引數指向的值相同,那麼用第二個引數取代第一個引數指向的值。函式返回值為原始值。
3. 為什麼單CPU的計算機不應該使用迴圈鎖?
舉例說明:
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 );
}
首先迴圈鎖會浪費CPU時間。CPU必須不斷地比較兩個值,直到一個值由於另一個執行緒而“奇妙地”改變為止。而且使用該迴圈鎖的執行緒都應該為同一優先順序,並且應當使用SetProcessPriorityBoost函式或 SetThreadPriorityBoost函式禁止執行緒優先順序的動態提高功能,否則優先順序較低的執行緒可能永遠不能被呼叫。
4. 如何使用volatile宣告變數?
如果是對共享資源的地址進行使用如&g_Resource那麼可以不使用 volatile,因為將一個變數地址傳遞給一個函式時,該函式必須從記憶體讀取該值。優化程式不會對它產生任何影響。如果直接使用變數,必須有一個 volatile型別的限定詞。它告訴編譯器,變數可以被應用程式本身以外的某個東西進行修改,這些東西包括作業系統,硬體或同時執行的執行緒等。 volatile限定詞會告訴編譯器,不要對該變數進行任何優化,並且總是重新載入來自該變數的記憶體單元的值。否則編譯器會把變數的值存入CPU暫存器,每次對暫存器進行操作。執行緒就會進入一個無限迴圈,永遠無法喚醒。
5. 如何使用關鍵程式碼段實現執行緒的同步?
如果需要一小段程式碼以原子操作的方式執行,這時簡單的互鎖函式已不能滿足需要,必須使用關鍵程式碼段來解決問題。不過使用關鍵程式碼段時,很容易陷入死鎖狀態,因為在等待進入關鍵程式碼段時無法設定超時值。關鍵程式碼段是通過對共享資源設定一個標誌來實現的,就像廁所門上的“有人/沒人”標誌一樣。這個標誌就是一個CRITICAL_SECTION變數。該變數在任何一個執行緒使用它之前應當進行初始化。初始化可以有兩種方法,使用InitializeCriticalSection函式和 InitializeCriticalSectionAndSpinCount函式。然後在每個使用共享資源的執行緒函式的關鍵程式碼段前使用 EnterCriticalSection函式或者使用TryEnterCriticalSection函式。在關鍵程式碼段使用之後呼叫 LeaveCriticalSection函式。在所有的執行緒都不再使用該共享資源後應當呼叫DeleteCriticalSection函式來清除該標誌。舉例說明:
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 );
……
}
使用關鍵程式碼段應當注意一些技巧:
(1) 每個共享資源使用一個CRITICAL_SECTION變數。
這樣在當前執行緒佔有一個資源時,另一個資源可以被其他執行緒佔有。
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) 同時訪問多個資源,必須始終按照完全相同的順序請求對資源的訪問。
這樣才能避免死鎖狀態產生。離開的順序沒有關係。
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) 不要長時間執行關鍵程式碼段。
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函式的返回值為空並且不會建立事件核心物件,比較節省系統資源,但是一旦發生兩個或多個執行緒爭用關鍵程式碼段的情況,如果記憶體不足,關鍵程式碼段可能被爭用,同時系統可能無法建立必要的事件核心物件。這時EnterCriticalSection函式將會產生一個EXCEPTION_INVALID_HANDLE異常。這個錯誤非常少見。如果想對這種情況有所準備,可以有兩種選擇。可以使用結構化異常處理方法來跟蹤錯誤。當錯誤發生時,既可以不訪問關鍵程式碼段保護的資源,也可以等待某些記憶體變成可用狀態,然後再次呼叫 EnterCriticalSection函式。
另一種選擇是使用InitializeCriticalSectionAndSpinCount,第二個引數dwSpinCount中,傳遞的是在使執行緒等待之前它試圖獲得資源時想要迴圈鎖迴圈迭代的次數。這個值可以是0至0x00FFFFFF之間的任何數字。如果在單處理器計算機上執行時呼叫該函式,該引數被忽略,並且始終設定為0。使用InitializeCriticalSectionAndSpinCount函式建立關鍵程式碼段,確保設定了 dwSpinCount引數的高資訊位。當該函式發現高資訊位已經設定時,它就建立該事件核心物件,並在初始化時將它與關鍵程式碼段關聯起來。如果事件無法建立,該函式返回FALSE。可以更加妥善地處理程式碼中的這個事件。如果事件建立成功,EnterCriticalSection將始終都能執行,並且決不會產生異常情況(如果總是預先分配事件核心物件,就會浪費系統資源。只有當程式碼不能容許EnterCriticalSection執行失敗,或者有把握會出現爭用現象,或者預計程式將在記憶體非常短缺的環境中執行時,才能預先分配事件核心物件)。
7. TryEnterCriticalSection和EnterCriticalSection的差別是什麼?
如果EnterCriticalSection將一個執行緒置於等待狀態,那麼該執行緒在很長時間內就不能再次被排程。實際上,在編寫得不好的應用程式中,該執行緒永遠不會再次被賦予CPU時間。TryEnterCriticalSection函式決不允許呼叫執行緒進入等待狀態。它的返回值能夠指明呼叫執行緒是否能夠獲得對資源的訪問權。TryEnterCriticalSection發現該資源已經被另一個執行緒訪問,它就返回FALSE。在其他所有情況下,它均返回TRUE。運用這個函式,執行緒能夠迅速檢視它是否可以訪問某個共享資源,如果不能訪問,那麼它可以繼續執行某些其他操作,而不必進行等待。如果 TryEnterCriticalSection函式確實返回了TRUE,那麼CRITICAL_SECTION的成員變數已經更新。Windows98 沒有可以使用的TryEnterCriticalSection函式的實現程式碼。
原文:http://dev.csdn.net/article/50/50454.shtm
讀後:
關鍵是:WaitForSingleObject函式.
WaitForSingleObject的用法
WaitForSingleObject 的用法
DWORD
WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
引數 hHandle 是一個事件的控制程式碼,第二個引數 dwMilliseconds 是時間間隔。如果時間是有訊號狀態返回 WAIT_OBJECT_0 ,如果時間超過 dwMilliseconds 值但時間事件還是無訊號狀態則返回 WAIT_TIMEOUT 。
hHandle 可以是下列物件的控制程式碼:
Change notification
Console input
Event
Job
Memory resource notification
Mutex
Process
Semaphore
Thread
Waitable timer
WaitForSingleObject 函式用來檢測 hHandle 事件的訊號狀態,當函式的執行時間超過 dwMilliseconds 就返回,但如果引數 dwMilliseconds 為 INFINITE 時函式將直到相應時間事件變成有訊號狀態才返回,否則就一直等待下去,直到 WaitForSingleObject 有返回直才執行後面的程式碼
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/25897606/viewspace-704360/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Android 基礎知識——執行緒Android執行緒
- 多執行緒基礎知識執行緒
- 多執行緒基礎知識點梳理執行緒
- Android小知識-Java多執行緒的基礎知識瞭解下AndroidJava執行緒
- java多執行緒基礎知識速通Java執行緒
- 多執行緒基礎必要知識點!看了學習多執行緒事半功倍執行緒
- Thread執行緒的基礎知識及常見疑惑點thread執行緒
- Java基礎知識回顧之五 ----- 多執行緒Java執行緒
- 多執行緒面試必備基礎知識彙總執行緒面試
- Java多執行緒程式設計基礎知識彙總Java執行緒程式設計
- 達夢資料庫基礎知識(四)管理DM執行緒資料庫執行緒
- [C#.NET 拾遺補漏]11:最基礎的執行緒知識C#執行緒
- java基礎之執行緒 認識volatileJava執行緒
- 玩轉java多執行緒 之多執行緒基礎 執行緒狀態 及執行緒停止實戰Java執行緒
- 執行緒基礎執行緒
- Redis基礎知識(學習筆記6--執行緒IO模型)Redis筆記執行緒模型
- 執行緒基本知識點執行緒
- 程式執行緒篇——程式執行緒基礎執行緒
- Java執行緒池一:執行緒基礎Java執行緒
- java基礎之執行緒 認識原子類Java執行緒
- 【轉】JavaScript物件的基礎知識JavaScript物件
- 多執行緒基礎執行緒
- Java 執行緒基礎Java執行緒
- Java 多執行緒基礎(四)執行緒安全Java執行緒
- 多執行緒系列(1),多執行緒基礎執行緒
- python基礎執行緒-管理併發執行緒Python執行緒
- 多執行緒系列(三):執行緒池基礎執行緒
- 前置知識—程式和執行緒執行緒
- Python程式與執行緒知識Python執行緒
- 多執行緒基礎-基礎實現執行緒
- 多執行緒程式設計基礎(一)-- 執行緒的使用執行緒程式設計
- 多執行緒(三)、執行緒池 ThreadPoolExecutor 知識點總結執行緒thread
- 多執行緒學習一(多執行緒基礎)執行緒
- Java 多執行緒基礎(八)執行緒讓步Java執行緒
- iOS開發基礎——執行緒安全(執行緒鎖)iOS執行緒
- pthread 多執行緒基礎thread執行緒
- python多執行緒基礎Python執行緒
- java - 多執行緒基礎Java執行緒
- 執行緒池知識點詳解執行緒