警告
從 Windows 11 起,不支援使用者模式排程。 所有呼叫都會失敗,錯誤 ERROR_NOT_SUPPORTED。
使用者模式排程 (UMS) 是一種輕量型機制,應用程式可用來排程自己的線程。 應用程式可以在使用者模式中切換UMS線程,而不需要涉及 系統排程器,並在核心中封鎖UMS線程時重新取得處理器的控制。 UMS 線程不同於 fibers,因為每個 UMS 線程都有自己的線程內容,而不是共用單個線程的線程內容。 在使用者模式中切換線程的能力,讓UMS比 線程集區更有效率, 管理需要少數系統呼叫的大量短期工作專案。
針對高效能需求的應用程式,建議使用UMS,這些應用程式需要有效率地在多處理器或多核心系統上同時執行許多線程。 若要利用 UMS,應用程式必須實作排程器元件,以管理應用程式的 UMS 線程,並判斷其何時應該執行。 開發人員應該考慮其應用程式效能需求是否合理化開發這類元件所涉及的工作。 使用中度效能需求的應用程式,可讓系統排程器排程其線程,以提供更好的服務。
UMS 適用於在 AMD64 和 Itanium 平台上運行的 64 位應用程式,可用於 Windows 7 和 Windows Server 2008 R2 版本,直到 Windows 10 版本 21H2 和 Windows Server 2022。 此功能不適用於 Arm64、32 位版本的 Windows 或 Windows 11。
如需詳細資訊,請參閱下列各節:
UMS 排程器
應用程式的UMS排程器負責建立、管理及刪除UMS線程,並判斷要執行的UMS線程。 應用程式的排程器會執行下列工作:
- 針對應用程式將執行UMS工作執行緒的每個處理器,建立一個UMS排程器執行緒。
- 建立UMS工作執行緒以執行應用程式的任務。
- 維持一個自己的已準備執行的工作線程佇列,並依據應用程式的排程策略選擇要執行的線程。
- 建立並監視一個或多個完成清單,系統會將核心中已完成處理的執行緒排入佇列。 其中包括新建立的工作線程,以及先前因系統呼叫而被封鎖並已解除的線程。
- 提供排程器進入點函式來處理來自系統的通知。 系統會在建立工作線程、背景工作線程在系統呼叫時阻塞或背景工作線程明確讓出控制權時,呼叫進入點函式。
- 針對已完成執行的背景工作線程執行清除工作。
- 當應用程式要求時,執行排程器的有序關機。
UMS 排程器線程
UMS 排程器線程是一般線程,透過呼叫 EnterUmsSchedulingMode 函式,將本身轉換成 UMS。 系統排程器會根據相對於其他就緒線程的優先順序,判斷UMS排程器線程何時執行。 排程器線程執行的處理器會受到線程親和性的影響,與非UMS線程相同。
EnterUmsSchedulingMode 的呼叫端 會指定一個完成清單和一個 UmsSchedulerProc 進入點函式,以與 UMS 排程器執行緒產生關聯。 系統完成將呼叫線程轉換成UMS時,會呼叫指定的進入點函式。 排程器進入點函式負責判斷指定線程的適當下一個動作。 如需詳細資訊,請參閱本主題稍後的 UMS 排程器入口函式。
應用程式可能會為每個將用來執行UMS線程的處理器建立一個UMS排程器線程。 應用程式可能也會針對特定邏輯處理器設定每個UMS排程器線程的關聯性,這通常會排除不相關的線程在該處理器上執行,有效地保留該處理器供該排程器線程使用。 請注意,以這種方式設定線程親和性可能會影響整體系統效能,因為會造成系統上可能正在執行的其他程序資源短缺。 如需線程親和性的詳細資訊,請參閱 多個處理器。
UMS 工作者線程、線程上下文和完成列表
UMS 工作線程是藉由呼叫 CreateRemoteThreadEx,並使用 PROC_THREAD_ATTRIBUTE_UMS_THREAD 屬性,指定 UMS 線程內容和完成清單來建立。
UMS 線程上下文代表工作線程的 UMS 線程狀態,並用來識別 UMS 函式呼叫中的工作線程。 其建立方式是呼叫 createUmsThreadContext 。
完成清單是藉由呼叫 CreateUmsCompletionList 函式來建立的。 完成清單會接收在核心中已完成執行並準備在使用者模式中運行的UMS工作線程。 只有系統可以將工作線程排入完成清單。 建立新的 UMS 工作線程時,這些線程會自動排入指定的完成清單中。 先前被封鎖的工作線程在不再被封鎖時也會加入完成清單。
每個 UMS 排程器線程都會與單一完成清單相關聯。 不過,相同的完成清單可以與任意數目的 UMS 排程器線程相關聯,而排程器線程可以從具有指標的任何完成清單中擷取 UMS 內容。
每個完成清單都有一個相關聯的事件,當系統將一或多個工作線程排入空清單時,就會發出通知。 GetUmsCompletionListEvent 函式會取得指定完成清單之事件的控制代碼。 應用程式可以等候多個完成清單事件,以及其他對應用程式有意義的事件。
UMS 進入點排程器函式
應用程式的排程器進入點函式會實作為 UmsSchedulerProc 函式。 系統會在下列時機呼叫應用程式的排程器入口函式:
- 當呼叫 EnterUmsSchedulingMode將非 UMS 線程轉換為 UMS 排程器線程時。
- 當 UMS 工作線程呼叫 UmsThreadYield時。
- 當 UMS 工作線程因系統服務而封鎖時,例如因系統呼叫或頁面錯誤。
UmsSchedulerProc 函式的 Reason 參數會指定呼叫進入點函式的原因。 如果因為已建立新的 UMS 排程器線程而呼叫進入點函式,SchedulerParam 參數會包含由呼叫者 EnterUmsSchedulingMode所指定的數據。 如果因為產生 UMS 背景工作線程而呼叫進入點函式,則 SchedulerParam 參數包含 由 umsThreadYield呼叫者所指定的數據。 如果因為核心中封鎖了UMS背景工作線程而呼叫進入點函式,則 SchedulerParam 參數為 NULL。
排程器進入點函式負責判斷指定線程的適當下一個動作。 當一個工作線程被封鎖時,排程器入口函數可能會執行下一個可用的就緒UMS工作線程。
呼叫排程器進入點函式時,應用程式的排程器應該藉由呼叫 DequeueUmsCompletionListItems 函式,嘗試擷取其相關聯完成清單中的所有專案。 此函式會擷取已在核心中完成處理的UMS線程內容清單,並準備好在使用者模式中執行。 應用程式的排程器不應該直接從此清單中執行 UMS 線程,因為這可能會導致應用程式中無法預期的行為。 相反地,排程器應該針對每個內容呼叫 GetNextUmsListItem 函式,在排程器的就緒線程佇列中插入 UMS 線程內容,然後才從就緒線程佇列中執行 UMS 線程。
如果排程器不需要等候多個事件,它應該呼叫 DequeueUmsCompletionListItems 具有非零逾時參數,讓函式在傳回之前等候完成清單事件。 如果排程器需要等候多個完成清單事件,它應該呼叫 DequeueUmsCompletionListItems,且具有零的逾時參數,因此函式會立即傳回,即使完成清單是空的。 在此情況下,排程器可以明確地等候完成清單事件,例如,使用 WaitForMultipleObjects。
UMS 線程執行
新建立的 UMS 工作者執行緒會排入指定的完成清單,直到應用程式的 UMS 排程器選取它執行後,才會開始運行。 這與非UMS線程不同,除非呼叫端明確建立線程暫停,否則系統排程器會自動排程執行。
排程器會透過使用背景工作線程的 UMS 內容呼叫 executeUmsThread ExecuteUmsThread 來執行背景工作線程。 UMS 工作線程會執行,直到呼叫 UmsThreadYield 函式、阻塞或終止為止。
UMS 最佳做法
實作 UMS 的應用程式應遵循下列最佳做法:
- UMS 線程內容的基礎結構是由系統管理,不應直接修改。 請改用 QueryUmsThreadInformation 和 SetUmsThreadInformation 來擷取和設定 UMS 背景工作線程的相關信息。
- 為了協助防止死結,UMS 排程器線程不應該與 UMS 工作線程共用鎖。 這包括應用程式建立的鎖定和系統鎖定,這些鎖定是透過如從記憶體堆配置或載入 DLL 等操作間接取得的。 例如,假設排程器會執行載入 DLL 的 UMS 背景工作線程。 工作執行緒獲取載入器鎖並阻塞。 系統會呼叫排程器進入點函式,然後載入 DLL。 這會導致死結,因為載入器鎖定已被保留,而且在第一個執行緒解除封鎖之前無法釋放。 為避免這個問題,請將可能與UMS工作執行緒共享鎖的工作,委派給專用的UMS工作執行緒或非UMS執行緒。
- 在使用者模式中完成大部分處理時,UMS 最有效率。 盡可能避免在UMS工作執行緒中進行系統調用。
- UMS 工作線程不應假設使用系統排程器。 此假設可能會產生一些細微的影響。例如,如果未知程式碼中的執行緒設定了其優先權或核心親和性,UMS 排程器仍可能會覆寫此設定。 假設系統排程器正在使用的程式代碼可能無法如預期般運作,而且可能會在UMS線程呼叫時中斷。
- 系統可能需要鎖定 UMS 工作線程的上下文。 例如,內核模式異步過程調用 (APC) 可能會變更 UMS 線程的內容,因此線程內容必須鎖定。 如果排程器在鎖定時嘗試執行UMS線程內容,呼叫將會失敗。 這種行為是設計使然,排程器應該被設計成重試對 UMS 執行緒內容的存取。