每個進程至少需要一個線程。
? ? ? ? 進程由兩部分構成:進程內核對象,地址空間。線程也由兩部分組成:線程內核對象,操作系統用它來對線程實施管理。線程堆棧,用于維護線程在執行代碼時需要的所有函數參數和局部變量。
? ? ? ? 進程是不活潑的。進程從來不執行任何東西,它只是線程的容器。線程總是在某個進程環境中創建的,而且它的整個壽命期都在該進程中。
? ? ? ? 如果在單進程環境中,有多個線程正在運行,那么這些線程將共享單個地址空間。這些線程能夠執行相同的代碼,對相同的數據進行操作。這些線程還能共享內核對象句柄,因為句柄表依賴于每個進程而不是每個線程存在。
進程使用的系統資源比線程多得多。實際上,線程只有一個內核對象和一個堆棧,保留的記錄很少,因此需要很少的內存。因此始終都應該設法用增加線程來解決編程問題,避免創建新的進程。但是許多程序設計用多個進程來實現會更好些。
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個線程。如果減少默認堆棧的大小,則可以創建更多的線程。
線程的調度、優先級和親緣性
9. ? 如何暫停和恢復線程的運行?
? ? 線程內核對象的內部有一個值指明線程的暫停計數。當調用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才是安全的。
10. ? 是否可以暫停和恢復進程的運行?
? ? ? ? 對于Windows來說,不存在暫停或恢復進程的概念,因為進程從來不會被安排獲得CPU時間。不過Windows確實允許一個進程暫停另一個進程中的所有線程的運行,但是從事暫停操作的進程必須是個調試程序。特別是,進程必須調用WaitForDebugEvent和ContinueDebugEvent之類的函數。由于競爭的原因,Windows沒有提供其他方法來暫停進程中所有線程的運行。
11. ? 如何使用sleep函數?
? ? ? ? 系統將在大約的指定毫秒數內使線程不可調度。Windows不是個實時操作系統。雖然線程可能在規定的時間被喚醒,但是它能否做到,取決于系統中還有什么操作正在進行。
可以調用Sleep,并且為dwMilliseconds參數傳遞INFINITE。這將告訴系統永遠不要調度該線程。這不是一件值得去做的事情。最好是讓線程退出,并還原它的堆棧和內核對象。可以將0傳遞給Sleep。這將告訴系統,調用線程將釋放剩余的時間片,并迫使系統調度另一個線程。但是,系統可以對剛剛調用Sleep的線程重新調度。如果不存在多個擁有相同優先級的可調度線程,就會出現這種情況。
12. ? 如何轉換到另一個線程?
? ? ? ? 系統提供了SwitchToThread函數。當調用這個函數的時候,系統要查看是否存在一個迫切需要CPU時間的線程。如果沒有線程迫切需要CPU時間,SwitchToThread就會立即返回。如果存在一個迫切需要CPU時間的線程,SwitchToThread就對該線程進行調度(該線程的優先級可能低于調用SwitchToThread的線程)。這個迫切需要CPU時間的線程可以運行一個時間段,然后系統調度程序照常運行。該函數允許一個需要資源的線程強制另一個優先級較低、而目前卻擁有該資源的線程放棄該資源。如果調用SwitchToThread函數時沒有其他線程能夠運行,那么該函數返回FALSE,否則返回一個非0值。調用SwitchToThread與調用Sleep是相似的。差別是SwitchToThread允許優先級較低的線程運行;而即使有低優先級線程迫切需要CPU時間,Sleep也能夠立即對調用線程重新進行調度。
13. ? 如何取得線程運行的時間?
(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時間。
14. ? 進程的優先級類有哪些?
優先級類 ? 標識符 ? 描述
實時 ? ? ? ? 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等開關。
15. ? 線程的相對優先級有哪些?
相對優先級 ? 標識符 ? 描述
關鍵時間 ? ? ? ? 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 ? );
16. ? 如何避免系統動態提高線程的優先級等級?
系統常常要提高線程的優先級等級,以便對窗口消息或讀取磁盤等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個函數的有用的實現代碼。