從處理器角度來看,執行緒是獨立的執行流程,各自擁有暫存器與堆疊上下文。在單核心處理器環境下,執行環境在一個時間片段內只能執行一個執行緒。當執行緒無法取得所需資源(如 I/O 作業)或用完時間片時,會發生「上下文切換」。當某執行緒進入「阻塞」狀態而系統切換執行其他執行緒時,有效減少處理器閒置時間,這就是「多任務」的運作原理。
程式執行時,系統會分配包含虛擬記憶體的位址空間,這個運行時上下文稱為「進程」。每個進程至少擁有一個執行緒——「主執行緒」,它與後續建立的執行緒本質上沒有差異,只是碰巧是第一個建立的。進程內的執行緒數量在運行時可動態變化,任何執行緒都能建立新執行緒,但所有執行緒皆屬於同一進程整體,共享相同位址空間與資源。當進程內最後一個執行緒結束時,父進程也隨之終止。
若進程只有一個執行緒,執行流程將自上而下循序執行;當執行緒阻塞且無其他活躍執行緒時,系統進入閒置狀態,造成大量等待時間浪費。實際應用中,進程通常包含多個可並行的「子任務」。例如列印假脫機程式:一個執行緒負責傳送列印工作到印表機,另一個處理用戶新請求,兩者獨立運作,避免輪詢機制的時間浪費與延遲權衡難題。
原子性:原子變數或物件作為整體被存取,甚至在非同步操作下也能保證邏輯完整性——要麼讀到舊值,要麼讀到新值,不會出現新舊值混雜。C++/CLI 大多數基本型別具備原子性,但複雜物件(如 Point 結構、陣列)不具備,需透過同步機制保護。
可重入性:可重入函數可被多執行緒安全平行執行。各執行緒在函數內分配的資料(來自堆疊或堆積)彼此獨立。但若函數存取執行緒間共享的變數或檔案,仍需同步方法。
以下範例展示主執行緒建立兩個子執行緒並行執行,無共享資料、無同步機制:
using namespace System;
using namespace System::Threading;
public ref class ThreadX
{
int loopStart, loopEnd, dispFrequency;
public:
ThreadX(int start, int end, int freq)
: loopStart(start), loopEnd(end), dispFrequency(freq) {}
void ThreadEntryPoint()
{
String^ name = Thread::CurrentThread->Name;
for (int i = loopStart; i <= loopEnd; ++i)
if (i % dispFrequency == 0)
Console::WriteLine("{0}: i = {1,10}", name, i);
Console::WriteLine("{0} thread terminating", name);
}
};
int main()
{
ThreadX^ o1 = gcnew ThreadX(0, 1000000, 200000);
Thread^ t1 = gcnew Thread(gcnew ThreadStart(o1, &ThreadX::ThreadEntryPoint));
t1->Name = "t1";
ThreadX^ o2 = gcnew ThreadX(-1000000, 0, 200000);
Thread^ t2 = gcnew Thread(gcnew ThreadStart(o2, &ThreadX::ThreadEntryPoint));
t2->Name = "t2";
t1->Start();
t2->Start();
Console::WriteLine("Primary thread terminating");
}
執行緒物件建構後處於未啟用狀態,需呼叫 Start() 才真正建立新執行流程。輸出結果顯示三個執行緒輸出交織穿插,主執行緒可能先於子執行緒結束,證明執行緒生命週期獨立於建立關係。
當多執行緒存取共享資源時,必須使用同步機制。C++/CLI 為每個物件提供同步鎖,透過 Monitor::Enter 與 Monitor::Exit 實現獨佔存取:
void Move(int xor, int yor)
{
Monitor::Enter(this);
X = xor; Y = yor;
Monitor::Exit(this);
}
同步鎖保證同一時間只有一個實例函數在物件上執行關鍵程式碼。鎖的作用範圍應盡可能縮小,避免不必要阻塞。釋放鎖時需注意異常處理,建議使用 try/finally 確保 Monitor::Exit 必被呼叫。
靜態函數同步可使用 typeid 操作符或專用靜態 Object^ Lock 物件:
static Object^ Lock = gcnew Object;
static void F3() { Monitor::Enter(Lock); /*...*/ Monitor::Exit(Lock); }
static void F4() { Monitor::Enter(Lock); /*...*/ Monitor::Exit(Lock); }
巢狀同步鎖會增加鎖計數,必須遞減至零才能被其他執行緒存取。不同物件的同步鎖可巢狀包含,但會阻塞直到內層物件可存取。