執行緒基本知識點

Mobidogs發表於2020-04-04


1. 程式與執行緒有那些區別和聯絡?
l 每個程式至少需要一個執行緒。
l 程式由兩部分構成:程式核心物件,地址空間。執行緒也由兩部分組成:執行緒核心物件,作業系統用它來對執行緒實施管理。執行緒堆疊,用於維護執行緒在執行程式碼時需要的所有函式引數和區域性變數。
l 程式是不活潑的。程式從來不執行任何東西,它只是執行緒的容器。執行緒總是在某個程式環境中建立的,而且它的整個壽命期都在該程式中。
l 如果在單程式環境中,有多個執行緒正在執行,那麼這些執行緒將共享單個地址空間。這些執行緒能夠執行相同的程式碼,對相同的資料進行操作。這些執行緒還能共享核心物件控制程式碼,因為控制程式碼表依賴於每個程式而不是每個執行緒存在。
l 程式使用的系統資源比執行緒多得多。實際上,執行緒只有一個核心物件和一個堆疊,保留的記錄很少,因此需要很少的記憶體。因此始終都應該設法用增加執行緒來解決程式設計問題,避免建立新的程式。但是許多程式設計用多個程式來實現會更好些。


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個執行緒。如果減少預設堆疊的大小,則可以建立更多的執行緒。
 

相關文章