多線程異步編程示例和實踐-Thread和ThreadPool
說到多線程異步編程,總會說起Thread、ThreadPool、Task、TPL這一系列的技術。總結整理了一版編程示例和實踐,分享給大家。
先從Thread和ThreadPool說起:
1. 創建並啟動線程
2. 暫停線程
當前線程在執行Thread.Sleep方法時,會等待指定的時間(1000ms)
此時,當前線程處於阻塞狀態:WaitSleepJoin
3. 線程等待
當程序運行時,啟動了一個耗時較長的線程打印數字,每次打印輸出前需要等待1000ms,我們在主程序中調用ThreadJoin方法,內部調用了thread.Join,該方法允許程序等待thread執行完成。
當thread線程執行完成後,主線程會繼續執行,輸出Thread Completed!
4. 線程終止
當主程序和單獨的數字打印線程運行時,主程序等待6000ms後對thread線程調用了Abort方法。這給線程觸發ThreadAbortException異常,導致線程被終止!
這個操作非常危險,因為該操作可以在任何時間發生並可能徹底摧毀應用程序。(Windows服務,因為線程(前臺線程)異常退出)
5.線程傳遞參數
6. 線程安全和Lock
線程安全就是多線程訪問時,采用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問直到該線程讀取完,其他線程才可使用。線程安全情況下,不會出現數據不一致或者數據汙染的問題。 線程不安全就是不提供數據訪問保護,有可能出現多個線程先後更改數據造成所得到的數據是臟數據! 若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。
lock 關鍵字通過獲取指定對象的互斥鎖,將語句塊標記為臨界區,執行語句然後釋放該鎖。
lock 確保當一個線程位於代碼的臨界區時,另一個線程不進入臨界區。如果其他線程試圖進入鎖定的代碼,則它將一直等待(即被阻止),直到該對象被釋放。使用Lock,會導致整個應用程序串行化,降低程序的並發能力,影響性能。
到底什麽場景下要使用lock保證線程安全:該串行就串行,該並行就並行。
7. 線程的異常捕獲和處理
在線程中執行異常處理。線程(甚至是後臺線程)中的未處理異常
通常會終止進程,因此,在線程內部使用try/catch代碼塊來捕獲異常,
不可能在線程之外通過try/catch捕獲異常
8. 線程池ThreadPool
核心類:System.Threading.ThreadPool, 線程池受.Net CLR管理的,每一個CLR都有一個線程池實例。
每個進程都有一個線程池。線程池的默認大小為:每個可用的處理器有 25 個線程。使用 SetMaxThreads 方法可以更改線程池中的線程數。每個線程使用默認的堆棧大小並按照默認的優先級運行。
ThreadPool類型擁有一個QueueUserWorkItem的靜態方法。該靜態方法接收一個委托,代表用戶自定義的一個異步操作。在改方法被調用後,委托會進入到內部隊列中。如果線程池中沒有任何線程,將創建一個新的工作者線程(worker thread)並將隊列中的第一個委托放入到該工作者線程中。
如果向線程池中放入新的操作,當之前的所有操作完成後,很可能只需重用一個線程來執行這些新的操作。如果QueueUserWorkItem執行的頻率過快,線程池將創建更多的線程來執行這些新放入的異步委托。
線程池中的線程數是有限的,如果沒有空閑的線程來執行這些異步委托操作,這種情況下,新的異步委托操作將在線程池的內部隊列中等待,直到線程池中年的工作者線程空閑(有能力)來執行。
當停止向線程池中放入新的異步委托操作時,線程池會刪除一定事件後過期的不在使用的線程,同時釋放不再使用的系統資源。
9. 不適合使用線程池的場景
在以下幾種情況下,適合於創建並管理自己的線程而不是使用線程池線程:
•需要前臺線程。
•需要使線程具有特定的優先級。
•任務會導致線程長時間被阻塞。由於線程池具有最大線程數限制,因此大量阻塞的線程池線程可能會阻止任務啟動。
•需要將線程放入單線程單元。所有 ThreadPool 線程均處於多線程單元中。
•需要具有與線程關聯的穩定標識,或使某一個線程專用於某一個任務。
周國慶
2017/6/8
多線程異步編程示例和實踐-Thread和ThreadPool