共用方式為


使用 C 和 Win32 進行多執行緒處理

Microsoft C/C++ 編譯器 (MSVC) 支援建立多執行緒應用程式。 如果您的應用程式需要執行會導致使用者介面沒有回應的昂貴作業,請考慮使用一個以上的執行緒。

使用 MSVC 時,有數種方式可以搭配多個執行緒進行程式設計:您可以使用 C++/WinRT 和Windows 執行階段程式庫、Microsoft Foundation Class (MFC) 程式庫、C++/CLI 和 .NET 執行時間,或 C 執行時間程式庫和 WIN32 API。 本文是關於 C 中的多執行緒。如需範例程式碼,請參閱 C 中的範例多執行緒程式。

多執行緒程式

執行緒基本上是透過程式執行的路徑。 這也是 Win32 排程的最小執行單位。 執行緒包含堆疊、CPU 暫存器的狀態,以及系統排程器執行清單中的專案。 每個執行緒都會共用所有進程的資源。

進程包含一或多個執行緒,以及記憶體中程式的程式碼、資料和其他資源。 一般程式資源是開啟的檔案、號志和動態配置的記憶體。 當系統排程器提供其中一個執行緒執行控制項時,程式就會執行。 排程器會決定哪些執行緒應該執行,以及執行緒應該執行的時間。 優先順序較低的執行緒可能需要等候較高優先順序的執行緒完成其工作。 在多處理器電腦上,排程器可以將個別執行緒移至不同的處理器,以平衡 CPU 負載。

進程中的每個執行緒都會獨立運作。 除非您讓執行緒彼此可見,否則執行緒會個別執行,而且不會察覺進程中的其他執行緒。 不過,共用常見資源的執行緒必須使用信號或其他處理序間通訊方法來協調其工作。 如需同步處理執行緒的詳細資訊,請參閱 撰寫多執行緒 Win32 程式

多執行緒的程式庫支援

CRT 的所有版本現在都支援多執行緒,但某些函式的非鎖定版本除外。 如需詳細資訊,請參閱 多執行緒程式庫效能 。 如需可連結至程式碼之 CRT 版本的相關資訊,請參閱 CRT 程式庫功能

多執行緒的 Include 檔

標準 CRT 包含檔案會在程式庫中實作時宣告 C 執行時間程式庫函式。 如果您的編譯器選項指定 __fastcall或__vectorcall 呼叫慣例,編譯器會假設應該使用暫存器呼叫慣例來呼叫所有函式。 執行時間程式庫函式會使用 C 呼叫慣例,而標準 include 檔案中的宣告會指示編譯器產生對這些函式的正確外部參考。

執行緒控制項的 CRT 函式

所有 Win32 程式至少有一個執行緒。 任何執行緒都可以建立其他執行緒。 執行緒可以快速完成其工作,然後終止,或者它可以在程式生命週期中保持作用中。

CRT 程式庫提供下列函式來建立和終止執行緒: _beginthread、_beginthreadex _endthread和_endthreadex

_beginthreadex_beginthread 式會建立新的執行緒,並在作業成功時傳回執行緒識別碼。 如果執行緒完成執行,執行緒就會自動終止。 或者,它可以使用 呼叫 _endthread_endthreadex 來終止本身。

注意

如果您從以 libcmt.lib 建置的程式呼叫 C 執行時間常式,則必須使用 _beginthread_beginthreadex 函式啟動執行緒。 請勿使用 Win32 函式 ExitThreadCreateThread 。 當多個執行緒在等候暫停執行緒完成對 C 執行時間資料結構的存取時,使用 SuspendThread 可能會導致死結。

_beginthread和_beginthreadex函式

_beginthreadex_beginthread 式會建立新的執行緒。 執行緒會與進程中的其他執行緒共用進程的程式碼和資料區段,但有自己的唯一暫存器值、堆疊空間和目前的指令位址。 系統會為每個執行緒提供 CPU 時間,讓進程中的所有線程都可以同時執行。

_beginthread_beginthreadex 類似于 WIN32 API 中的 CreateThread 函式,但有下列差異:

  • 它們會初始化特定 C 執行時間程式庫變數。 只有當您線上程中使用 C 執行時間程式庫時,才很重要。

  • CreateThread 有助於控制安全性屬性。 您可以使用此函式來啟動處於暫停狀態的執行緒。

_beginthread 如果 _beginthreadex 成功,則傳回新執行緒的控制碼,如果發生錯誤,則傳回錯誤碼。

_endthread和_endthreadex函式

_endthread 函式會終止 由 _beginthread 建立的執行緒(同樣地, _endthreadex 終止 由 建立的 _beginthreadex 執行緒)。 執行緒會線上程完成時自動終止。 _endthread_endthreadex 適用于執行緒內的條件式終止。 例如,專用於通訊處理的執行緒,如果無法控制通訊埠,就可以結束。

撰寫多執行緒 Win32 程式

當您撰寫具有多個執行緒的程式時,您必須協調其行為和使用 程式的資源 。 此外,請確定每個執行緒都會收到 自己的堆疊

線上程之間共用一般資源

注意

如需 MFC 觀點的類似討論,請參閱 多執行緒:程式設計提示 多執行緒:使用同步處理類別 的時機。

每個執行緒都有自己的堆疊和自己的 CPU 暫存器複本。 進程中的所有線程都會共用其他資源,例如檔案、靜態資料和堆積記憶體。 使用這些常見資源的執行緒必須同步處理。 Win32 提供數種方式來同步處理資源,包括弧志、重要區段、事件和 Mutex。

當多個執行緒存取靜態資料時,您的程式必須提供可能的資源衝突。 請考慮一個程式,其中一個執行緒會更新靜態資料結構,其中包含 x,y 座標,讓另一個執行緒顯示專案。 如果更新執行緒改變 x 座標,並在變更 y 座標之前先占,則顯示執行緒可能會在更新 y 座標之前 排程。 專案會顯示在錯誤的位置。 您可以使用號志來控制結構的存取權,以避免這個問題。

Mutex (mutal ex clusion 的 簡稱)是線上程或進程之間以非同步方式執行的方式進行通訊。 此通訊可用來協調多個執行緒或進程的活動,通常是藉由鎖定和解除鎖定資源來控制共用資源的存取權。 若要解決這個 x,y 座標更新問題,更新執行緒會設定 mutex,指出資料結構在執行更新之前正在使用中。 在處理這兩個座標之後,它會清除 Mutex。 顯示執行緒必須在更新顯示器之前等待 mutex 清除。 此等候 Mutex 的程式通常稱為 在 Mutex 上封鎖 ,因為進程遭到封鎖,而且在 Mutex 清除之前無法繼續。

範例多執行緒 C 程式中顯示的 Bounce.c 程式 會使用名為 ScreenMutex 的 Mutex 來協調螢幕更新。 每次其中一個顯示執行緒準備好寫入畫面時,它會使用 的控制碼 ScreenMutex 和常數 INFINITE 呼叫 WaitForSingleObject ,以指出 WaitForSingleObject 呼叫應該封鎖在 mutex 上,而不是逾時。如果 ScreenMutex 清楚,等候函式會設定 mutex,讓其他執行緒無法干擾顯示,並繼續執行執行緒。 否則,執行緒會封鎖直到 Mutex 清除為止。 當執行緒完成顯示更新時,它會呼叫 ReleaseMutex 來釋放 Mutex。

螢幕顯示和靜態資料只是兩個需要謹慎管理的資源。 例如,您的程式可能會有多個執行緒存取相同的檔案。 因為另一個執行緒可能已經移動檔案指標,因此每個執行緒必須在讀取或寫入之前重設檔案指標。 此外,每個執行緒都必須確定它不會在放置指標的時間和存取檔案的時間之間先占。 這些執行緒應該使用號志來協調檔案的存取,方法是使用 和 ReleaseMutex 呼叫來加上括弧來協調檔案的存取 WaitForSingleObject 。 下列程式碼範例說明這項技術:

HANDLE    hIOMutex = CreateMutex (NULL, FALSE, NULL);

WaitForSingleObject( hIOMutex, INFINITE );
fseek( fp, desired_position, 0L );
fwrite( data, sizeof( data ), 1, fp );
ReleaseMutex( hIOMutex);

執行緒堆疊

所有應用程式的預設堆疊空間都會配置給執行的第一個執行緒,這稱為執行緒 1。 因此,您必須指定為程式所需的每個額外線程配置個別堆疊的記憶體數量。 如有必要,作業系統會為執行緒配置額外的堆疊空間,但您必須指定預設值。

呼叫中的 _beginthread 第一個引數是函式的 BounceProc 指標,它會執行執行緒。 第二個引數會指定執行緒的預設堆疊大小。 最後一個引數是傳遞至 BounceProc 的識別碼。 BounceProc 會使用識別碼來植入亂數產生器,並選取執行緒的色彩屬性和顯示字元。

對 C 執行時間程式庫或 WIN32 API 進行呼叫的執行緒,必須允許其呼叫的程式庫和 API 函式有足夠的堆疊空間。 C printf 函式需要超過 500 個位元組的堆疊空間,而且呼叫 WIN32 API 常式時應該有 2K 個位元組的堆疊空間可用。

因為每個執行緒都有自己的堆疊,因此您可以使用盡可能少的靜態資料,避免資料項目發生潛在的衝突。 將您的程式設計成針對執行緒可以私用的所有資料使用自動堆疊變數。 Bounce.c 程式中唯一的全域變數是 Mutex 或變數,這些變數在初始化之後永遠不會變更。

Win32 也提供執行緒本機儲存體 (TLS) 來儲存每個執行緒的資料。 如需詳細資訊,請參閱 執行緒本機儲存體 (TLS)

避免具有多執行緒程式的問題區域

建立、連結或執行多執行緒 C 程式時可能會遇到幾個問題。 下表說明一些較常見的問題。 (如需 MFC 觀點的類似討論,請參閱 多執行緒:程式設計提示 。)

問題 可能的原因
您會收到訊息方塊,顯示程式造成保護違規。 許多 Win32 程式設計錯誤會造成保護違規。 保護違規的常見原因是將資料間接指派給 Null 指標。 因為它會導致程式嘗試存取不屬於它的記憶體,所以會發出保護違規。

偵測保護違規原因的簡單方法是使用偵錯資訊編譯器,然後在 Visual Studio 環境中透過偵錯工具執行程式。 發生保護錯誤時,Windows 會將控制權傳輸至偵錯工具,而游標位於造成問題的行上。
您的程式會產生許多編譯和連結錯誤。 您可以將編譯器的警告層級設定為其中一個最高值,並注意警告訊息,藉以消除許多潛在問題。 藉由使用層級 3 或層級 4 警告層級選項,您可以偵測非預期的資料轉換、遺漏函式原型,以及使用非 ANSI 功能。

另請參閱

舊版程式碼的多執行緒支援 (Visual C++)
C 中的多執行緒程式範例
執行緒本機儲存體 (TLS)
透過 C++/WinRT 的並行和非同步作業
使用 C++ 和 MFC 進行多執行緒處理