目錄
一、線程
1.1 線程的基本概念
1.2 何時創建線程
二、線程控制
三、遍歷線程
四、線程內核對象
4.1 線程上下文
4.2 暫停次數
4.3 信號
五、線程調度
5.1 什么是線程優先級
5.2 進程優先級與相對線程優先級
5.3 編程改變優先級
5.4 動態優先級的概念
一、線程
1.1 線程的基本概念
????????線程是進程中的一個獨立控制流,它是程序運行時的調度單位,也是執行代碼的基本單元。從 CPU 的角度來看,所有線程都是平等的,不會因為屬于某個進程而受到特殊對待。CPU 根據線程的優先級來決定執行哪個線程,并分配相應的時間片。當一個線程的時間片用完時,CPU 會切換到下一個線程繼續執行。?
????????通常情況下,線程執行的代碼屬于某個進程,同一個進程中的所有線程共享進程的資源,包括虛擬內存空間、內核對象句柄表等。
????????此外,還有一種內核線程,它的代碼完全在內核空間中運行,不在我們當前的討論范圍內。每個線程都是一個獨立的執行單元。
1.2 何時創建線程
通常在以下情況下需要創建線程:
- 在輸入的同時需要輸出(例如在顯示界面的同時處理數據);
- 邏輯A的執行依賴于邏輯B的實時反饋(比如進度條的更新);
- 利用計算機的空閑時間處理一些繁重但不緊急的任務;
- 涉及大量磁盤操作的程序;
- 與網絡操作相關的程序;
- 需要處理多個長時間等待的業務邏輯;
- 需要進行密集計算,并充分利用多核CPU性能的程序;
- 需要讓其他進程執行自己的代碼時(例如遠程注入代碼);
????????簡單來說,當某些操作可能會阻塞當前線程,而當前線程又非常重要不能被阻塞時,就需要創建新的線程。
二、線程控制
相關的線程控制函數如下:
- `CreateProcess`:創建進程。
- `OpenProcess`:打開進程。
- `ExitProcess`:退出本進程。
- `TerminateProcess`:結束其他進程。
- `SuspendThread`:暫停線程(掛起)。
- `ResumeThread`:恢復線程。
創建線程示例代碼:
#include "stdafx.h"
#include <windows.h>
int g_nNum = 0;DWORD WINAPI ThreadProc(LPVOID lParam) {printf("我是線程!\n");for (int i=0;i<1000;i++){g_nNum++;}printf("%d\n", g_nNum);return 0;
}int main()
{CreateThread(NULL, NULL, ThreadProc, NULL, 0, NULL);CreateThread(NULL, NULL, ThreadProc, NULL, 0, NULL);system("pause");printf("%d\n", g_nNum);return 0;
}
三、遍歷線程
????????線程的遍歷同樣可以使用創建快照的方式,以下是遍歷某一個進程線程的示例代碼:
VOID ListProcessThreads(DWORD dwPID) {HANDLE hThreadSnap = INVALID_HANDLE_VALUE;THREADENTRY32 te32;//創建快照,當前操作系統的線程快照hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);if (hThreadSnap == INVALID_HANDLE_VALUE)return;//設置輸入參數,結構的大小te32.dwSize = sizeof(THREADENTRY32);//開始獲取信息if (!Thread32First(hThreadSnap, &te32)) {CloseHandle(hThreadSnap); //關閉句柄return;}do {if (te32.th32OwnerProcessID == dwPID) {//顯示相關信息printf("\n THREAD ID=0x%08X", te32.th32ThreadID);printf("\t base priority =%d", te32.tpBasePri);printf("\t delta priority =%d", te32.tpDeltaPri);}} while (Thread32Next(hThreadSnap, &te32));CloseHandle(hThreadSnap);
}
????????這段代碼的功能是列出指定進程id(通過 dwPID
參數標識)的所有線程信息。它使用了 Windows API 中的 Toolhelp32
?系列函數來遍歷系統中的線程,并輸出每個線程的?ID?和優先級信息。?
四、線程內核對象
????????線程的創建伴隨著在內核中創建一個線程內核對象的過程,在線程內核對象的數據結構中包含了一些重要信息。
4.1 線程上下文
????????對于CPU來說,系統里有大量線程等著被執行。CPU會依據線程的優先級,來決定先執行哪個線程,并且在執行前給它分配一定的時間片。當正在執行的線程把時間片用完了,CPU就會切換去執行其他線程,就這樣循環往復,讓操作系統里的線程都有機會依次執行。而在這個切換線程的過程中,就會涉及到線程環境的保存和加載操作。?
????????具體來講,當一個線程暫時不使用CPU了,它得把自己當前的執行環境保存起來,這樣下次再輪到它執行的時候,就能接著之前的狀態繼續。相反,當一個線程即將要被CPU執行時,得先把它對應的執行環境加載到CPU里。?
????????在線程的內核對象中,存在一個結構體專門用來存儲當前線程的執行環境。在用戶層面,我們可以借助特定的API來獲取和設置線程環境 。?
?
獲取線程環境的函數:
BOOL WINAPI GetThreadContext(_In_ HANDLE hThread,_Inout_ LPCONTEXT lpContext
);
?
設置線程環境的函數:
BOOL WINAPI SetThreadContext(_In_ HANDLE hThread,_In_ CONST CONTEXT* lpContext
);
`CONTEXT`結構體定義如下:
typedef struct _CONTEXT {DWORD ContextFlags;DWORD Dr0;DWORD Drl;DWORD Dr2;DWORD Dr3;DWORD Dr6;DWORD Dr7;FLOATING_SAVE_AREA FloatSave;DWORD SegGs;DWORD SegFs;DWORD SegEs;DWORD SegDs;DWORD Edi;DWORD Esi;DWORD Ebx;DWORD Edx;DWORDDWORDDWORDDWORDDWORDDWORDDWORDDWORDBYTE }CONTEXT;Ecx;Eax;Ebp;Eip;SegCs; EFlags;Esp;SegSs;ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
????????可以看出,線程環境基本就是CPU在執行此線程時的寄存器信息。
4.2 暫停次數
????????一個線程能夠被暫停,且能被暫停多次。一個線程被暫停的次數在線程的內核對象中有記錄,每暫停一次線程,暫停次數自增,每恢復一次線程,暫停次數自減,只有暫停次數為0的時候,線程才會運行起來。
4.3 信號
在創建和等待線程時會涉及信號的概念。例如:
//創建一個線程
HANDLE hThread = INVALID_HANDLE_VALUE;
CreateThread(NULL, NULL, Proc, NULL, NULL, NULL);//等待線程結束
WaitForSingleObject(hThread, -1);
//關閉句柄
CloseHandle(hThread);
????????信號其實就是線程自身的一種狀態。有個`WaitForSingleObject`函數,它專門用來檢查這個信號狀態。要是信號狀態是`FALSE` ,也就是沒有信號,處于非激發態,那`WaitForSingleObject`函數就會一直等著。而一旦信號狀態變成`TRUE`,也就是有信號,處于激發態了,這個函數就會馬上返回。就拿線程來說,當線程運行結束的時候,它的信號狀態就會從沒有信號的狀態轉變成有信號的狀態。這時候,`WaitForSingleObject`函數就能等到線程結束,然后返回,程序也就可以接著去執行后續的代碼了。?
????????不光線程是可等待的,還有很多內核對象也是可以等待的,如下表:
五、線程調度
5.1 什么是線程優先級
(1)每個線程都有一個優先級數值,范圍從0(最低)到31(最高)。
(2)當一個線程把分配給它的時間片用完了,系統就得決定接下來給哪個線程分配時間片。這時,系統會從優先級最高的線程開始,依次往低優先級查看,優先讓高優先級的線程得到執行機會。?
(3)要是有個低優先級的線程正在運行,可恰好有高優先級的線程已經準備好要執行了,那系統就會馬上中斷低優先級線程,轉而給高優先級線程分配時間片,讓高優先級線程運行起來。比如說,只要系統里存在優先級為31的線程,那系統就不會去執行那些優先級在0到30之間的線程。
(4)系統剛啟動的時候,會有一個特殊的線程,它的優先級是0 ,這個線程專門負責把系統內存里所有閑置的頁面都清零,而且整個系統里只有這一個優先級為0的線程 。
5.2 進程優先級與相對線程優先級
????????在Windows系統里,我們沒辦法直接去調整線程的優先級。Windows采用了一種間接的辦法,它依據進程優先級以及相對線程優先級,讓操作系統自己來算出線程優先級具體是多少。
????????進程優先級有這么幾種類型,分別是`real-time`(實時)、`high`(高)、`above normal`(高于標準)、`normal`(標準)、`below normal`(低于標準)、`idle`(最低) 。
????????進程優先級就像是給這個進程里所有線程的優先級定了個大方向。之后,再結合相對線程優先級,就能確定線程真正的優先級數值。
????????這里要特別注意,線程優先級是相對進程優先級而言的。要是你更改了進程優先級,雖然線程的相對優先級不會變,但是它實際的優先級數值卻是會跟著改變的 。
5.3 編程改變優先級
1. 當我們使用`CreateProcess`函數創建進程時,能在它的`fdwCreate`參數里設置優先級。
2. 要是想改變進程的優先級,還可以用`SetPriorityClass`這個函數來實現。
3. 要是想知道某個進程的優先級是多少,就得調用`GetPriorityClass`函數。
4. 注意,`CreateThread`函數沒有設置線程相對優先級的參數,不能通過它來設置線程相對優先級。
5. 要設置線程的優先級,得使用`SetThreadPriority`函數。
6. 要是想獲取線程的相對優先級,就得調用`GetThreadPriority`函數。
5.4 動態優先級的概念
1. 由進程優先級和線程優先級算出來的優先級,我們叫它基本優先級。有時候,系統會自動提高某個線程的優先級,這么做主要有兩個原因:
??????? (1)為了能快點響應一些緊急操作。
??????? (2)給那些一直沒機會執行、處于“饑餓”狀態的線程分配時間片。
2. 這里有幾點要注意:
????????(1)優先級被提高的線程,在得到時間片去執行后,它的優先級數值會自動減小,一直減到基本優先級就不再減了。
????????(2)如果一個“饑餓”線程有2到3秒都沒執行,它的優先級會臨時變為15,等它執行完兩個時間片后,優先級又會變回原來的數值。
????????(3)系統自動提升優先級的范圍是1到15,優先級在16到31之間的線程,系統不會去提升它們的優先級。
????????(4)我們能阻止系統提升優先級:
????????- 使用`SetProcessPriorityBoost()`函數,能禁止提升當前進程里線程的優先級。
??????? - 使用`SetThreadPriorityBoost()`函數,能禁止提升某個線程的優先級。
??????? - 要是想知道進程或線程有沒有使用優先級提升功能,可以用`GetProcessPriorityBoost`和`GetThreadPriorityBoost`這兩個函數來判斷 。