王靖WANG Jing;楊成YANG Cheng
(①北京信息職業技術學院信息工程系,北京 100018;②北京星網銳捷網絡技術有限公司,北京 100082)
(①Department of Information Engineering,Beijing Information Teconology College,Beijing 100018,China;②Beijing Star-net Communication Co.,Ltd.,Beijing 100082,China)
在現代應用程序中,為了用戶界面的更加友好,程序運行的更加流暢,使用多線程進行任務的處理已經是主流的選擇。但是多線程程序開發中,我們往往會遇到以下問題:
①在訪問共享的數據時,使用加鎖方式實現。但這種方式在程序規模增長到一定程度后,會不可避免地帶來程序的低效、死鎖等問題。②簡單地為每個耗時的任務創建一個線程;這種模式首先造成資源的浪費,其次當大量線程被創建出后,會耗盡系統的資源從而導致系統變慢或死機;即使是正常關閉,大量線程在關閉時的等待也將是一個漫長的過程。③任務執行完后,通過回調通知發起者任務,但可能發起者已被銷毀,從而導致程序的崩潰。
采用線程模型,及約定開發人員以事先制定的模式工作,能避免上述問題。線程模型的設計目標概括為以下幾點:
①除了在線程模型的管理和調度模塊,整個應用程序避免使用任何形式的鎖。這樣能避免程序低效、死鎖等問題。②線程模型應提供能動態調整的線程池來執行用戶提交的任務。③線程模型應提供統一的接口讓用戶提交任務、設定定時任務、設定任務過期條件、取消任務、存儲任務和提供回調等,避免在每個模塊做同樣的工作。④合理調度任務,保證共享數據的安全。⑤線程模型應管理每個服務和回調的生命周期,使應用
程序避免崩潰。
如圖1 所示,用戶任務通過線程模型接口提交,提交過程是異步的,可立即返回;任務通過線程模型接口,添加到服務存儲和管理隊列;任務分派程序會選擇合適的任務提交到線程池中,執行用戶的任務,完成時調用回調函數;通過生命周期管理,當回調的對象銷毀時,自動取消回調。

圖1 線程模型圖示
一個完整的線程模型至少應包含以下組成部分:
①線程模型接口;②任務存儲和調度線程的管理;③任務的分派;④線程池的管理;⑤線程模型的輔助設施。
1.1.1 創建線程模型管理 線程管理模塊在主程序入口處被創建。確保用戶的各個模塊都能調用到線程模型的各種接口。
1.1.2 銷毀線程模型管理 線程管理模塊在主程序出口處被銷毀。銷毀時,線程管理模塊保證正在被執行的任務執行完,同時取消正在隊列中等待的所有任務。
1.1.3 添加一個新的任務 線程模型提供輔助設施來協助用戶創建任務。任務將有6 個屬性:command,callback,task_id,group_id,priority 和timeout。
1.1.4 設定任務分組 如果有些任務需要訪問共享的數據,這些任務將按順序被放入線程池執行,避免多線程同時訪問共享數據。通過給這些任務賦予相同的ID,任務分派模塊就會自動的將相同ID 的任務順序放入線程池。
1.1.5 設定任務回調 任務回調在任務執行完成后被工作線程調用,回調過程如需線程切換,可通過線程模型的輔助設施來實施。
1.1.6 設置任務優先級 每項任務都有自己的優先級,高優先級的任務將優先被放進線程池執行。
1.1.7 任務超時 有的任務會有一個執行的期限,如超過這個期限,回調函數將被執行。
1.1.8 取消任務 用戶發起任務后,在等待任務執行的過程中,可能需要取消任務。此時,如任務在等待的隊列中,任務將被刪除。如任務已在線程池中被執行,則斷開該任務連接的回調函數。
1.1.9 重試任務 在某個任務失敗后,回調函數會通知用戶此次執行失敗和失敗的原因。用戶可通過重試該項任務,重新將任務發送到存儲隊列中等待執行。
1.1.10 預約任務 用戶希望在一段時間后啟動某些項任務,需要接口支持預約任務。
1.2.1 任務及其回調的存儲 采用多索引容器(boost::multi_index_container)形式來存儲任務,同時按照task id,priority 和group id 為任務建立不同的索引。這樣,不但在查找相應任務時效率更高,而且也保證了插入或者刪除數據時候的效率。[1]
1.2.2 任務管理及回調 ①由于用戶可能在任意的線程調用線程模型,來添加希望的服務。為避免本文開始提到的對共享數據加鎖的問題,需將任務的添加工作切換到任務管理線程執行。②維護用戶任務的狀態,Scheduling,Pending or Processing 也需在管理線程進行。③在回調發生的時候,需移除相應任務并觸發用戶預先設定的回調。回調必須在管理線程中執行,需檢查管理線程中該任務是否被取消。④用戶可能不斷添加新任務,線程池會添加任務完成的事件到管理線程。同時,用戶可能會取消之前添加的任務。以上操作會影響到共同數據,因此必須按順序執行。但這樣會導致大量添加新任務的操作,導致分派任務一直無法得到執行;在這種情況下,管理線程一直處于忙碌狀態,但是線程池卻處于空閑狀態。因此,對于不同的任務的添加,也需設定優先級。一般來說,完成任務的優先級設為Medium,用戶取消任務的優先級設為High,而添加任務的優先級設定為Low。
1.2.3 防止任務無限制占據線程 對線程模型來說,用戶創建的任務是不可控的。因此,會發生由于用戶任務錯誤導致線程池的線程進入死循環,使得線程喪失繼續服務的能力。線程調度管理程序如不能及時發現死去的線程,將有可能導致線程池所有線程被占用,從而導致用戶所有任務均無法執行。一般可以記錄上次該線程回調發生的時間。如超過指定時間范圍而無響應,可強制該線程關閉后重啟或者關閉相應任務,并重新添加線程到線程池。
1.2.4 定時器組件 為實現用戶預約任務,必須實現Timeout 部件,并在到期時,將回調的執行過程控制在管理線程中。實踐中,可考慮用Boost::asio::deadline_timer。
1.2.5 內存池的管理 當等待任務多時,增加線程池中線程的數量。當等待隊列很少或為空時,減少線程數量。增加減少不宜太頻繁。一般根據一段時間內處理的任務數來決定開啟的線程數。
1.2.6 任務回調的生命周期管理 對于回調任務,一般需做兩件事。第一,確?;卣{發生在指定線程。這一點,1.4 節將會專門講述。第二,確?;卣{所依賴的對象存在;如所依賴的對象已被銷毀,那么就取消該回調。實現可采用boost::signal 模式,只要求回調所依賴的目標對象從boost::signals::trackable[2]派生即可。
①分派單元的運行需確保在任務管理線程中執行。②分派單元按優先級取任務,放入隊列中執行。③如果標記為某個group id 的任務已在線程池中運行,那么該任務結束前,同樣group id 的任務不能被再次放入。④添加任務時和complete task 時均可嘗試重新分派任務。
①啟動指定數目的線程。②任務能夠通過接口添加到線程池的隊列中。③運行時動態增減線程數量。④退出時確保運行中的任務執行完畢。
①創建任務。②創建回調命令。③提供Factory 機制,使目標線程可以注冊相應的命令到Factory。該命令可將任意命令切換到線程執行。
線程模型執行的過程如圖2 所示。

圖2 線程模型執行過程
線程模型的使用者通過接口創建線程模型并拿到需要的接口。通過線程模型提供的輔助函數生成任務后,調用線程模型接口,把任務添加到線程模型管理的任務隊列。管理線程,在任務隊列不為空時,選擇合適的任務,并將完成任務的事件和任務命令綁定。將組裝好的命令放入線程池中去運行。執行完畢后,完成任務的事件被觸發,并切換到管理線程。該事件將進行下一輪任務分派。
對于不同的應用場合,線程模型有著不同的優化策略。優化策略一般考慮的環節有:
①是否充分利用每個線程的執行能力。②是否最大限度地減少了任務在線程之間的調度。③有些任務只讀共享的數據,有些需寫那些數據。如果能將讀寫任務區分對待,那么讀數據的任務就可以同時添加到線程池中。④調度管理程序處理添加和完成任務的優先順序,及任務的存儲結構。⑤任務隊列的動態規劃。
無論在客戶端UI 編程,還是在服務端編程,線程模型都是一個非常重要的設施,能提高程序的穩定性和可維護性。對于規模較大的系統,這是一項非常重要的基礎設施。本文結合在工程中的實踐經驗,詳盡分析了設計一個線程模型時需考慮的目標、結構、接口及模型的工作流。實踐中,這種線程模型能幫助應用程序簡化設計,提高穩定性,提升效率。
[1]王鳳嶺.分布式操作系統中線程包實現方法的對比研究[J].南寧職業技術學院學報,2004(04).
[2]陳矯陽,陳楸,劉桓龍.基于LabWindows/CVI 多線程數據采集的研究[J].科學技術與工程,2008(09).
[3]周仕祥,劉伯恕.Boost 功率因數校正器的效率和空載損耗研究[J].電力電子技術,2003(03).
[4]肖和平,韓偉紅,賈焰,吳泉源.StarCCM2.0 中高性能線程池模型的研究與實現[J].計算機工程,2005(24).