共用方式為


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

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

使用 MSVC 時,有數種方式可以搭配多個線程進行程式設計:您可以使用 C++/WinRT 和 Windows 執行階段 連結庫、Microsoft基礎類別 (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 exclusion 的簡稱)是在線程或進程之間以異步方式執行的方式進行通訊。 此通訊可用來協調多個線程或進程的活動,通常是藉由鎖定和解除鎖定資源來控制共用資源的存取權。 若要解決這個 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 進行多執行緒處理