C#線程基礎在前幾篇博文中都介紹了,如今最後來挖掘一下線程池的管理機制,也算爲這個線程基礎作個完結。算法
咱們如今都知道了,線程池線程分爲工做者線程和I/O線程,他們是怎麼管理的?數據結構
對於Microsoft設計的CLR線程池,線程池會隨着CLR的每一個版本的發佈,都會發生變化,很難去挖掘,這裏的提議是:多線程
最好將線程當作一個黑盒。不要拿單個應用程序去衡量這個黑盒的性能,由於它對任何一個應用程序來講都沒法作到完美。架構
相反,它是一種常規用途的線程調度技術,面向大量應用程序;它對某些應用程序的效果要好於其餘應用程序。性能
目前,它的工做狀況很是理想,這裏建議你信任它,由於你很難高出一個比CLR自帶的那個更好的線程池。另外,隨着時間的推移,線程池代碼內部,會更改它管理線程的方式,因此大多數應用程序的性能會變得愈來愈好。線程
CLR容許開發人員設置線程池建立最大線程數。而後有些開發人員感受好像有必要對線程池擁有的線程數量進行限制,由於有些人以爲,要合理利用資源,作到本身調配資源,是頗有成就感的事(是否是強迫症?)設計
但實踐證實,線程池永遠都不該該爲池中的線程數設置上限,由於可能發生飢餓或死鎖。blog
爲何這麼說?隊列
假如隊列中有1000個工做項,但這些工做項全都由於一個事件而阻塞(多麼可怕的事),等到第1001個工做項發出信號才能解除阻塞。若是設置最大1000個線程,第1001個線程就不會執行,因此1000個線程會一直阻塞,而後你能想到的,用戶被迫終止應用程序,並丟失他們的全部未保存的工做。你不能讓線程阻塞!進程
因爲存在飢餓和死鎖問題,因此CLR團隊一直都在穩步的增長線程池默認能擁有的最大線程數。
目前默認值是最大1000個。這能夠當作是不限數量,爲何?
一個32位進程最大的2GB的可用地址空間,加載了一組Win32和CLR DLLs,並分配了本地堆和託管堆以後,剩餘約1.5GB的地址空間。因爲每一個線程都要爲用戶模式棧和線程環境塊準備超過1MB的內存,因此在一個32位的進程中,最多能有1360個線程。試圖建立更多線程,則會拋出OutMemoryException。
一個64位進程提供了8TB的地址空間,因此理論上能夠建立千百萬個線程。可是分配這麼多線程,純屬浪費,尤爲是當理想線程數等於機器的CPU數的時候。
ThreadPool類提供了幾個靜態方法,調用它們能夠設置和查詢線程池的線程數:GetMaxThreads,SetMaxThreads,GetMinThreads和GetAvailableThreads。這裏建議你,不要調用上述任何方法,限制線程池的線程數,通常只會形成應用程序的性能變得更差,而不會變得更好。
若是你認爲本身的應用程序須要幾百個或者幾千個線程,那隻代表,你的應用程序的架構和使用線程的方式已出現嚴重的問題。
如今來看看如何管理工做者線程,以前須要來看看CLR線程池是什麼樣的:
這是工做者線程的數據結構。ThreadPool.QueueUserWorkItem方法和Timer類老是會將工做項放到全局隊列中。
而工做線程採用一個先入先出(FIFO)算法將工做項從這個隊列取出,並處理它們。(學過數據結構的應該知道FIFO)
因爲多個工做者線程可能同時從全局隊列中拿走工做項,因此全部工做者線程都競爭一個線程同步鎖,以保證兩個或多個線程不會獲取同一個工做項。同步鎖在某些應用程序總可能對伸縮性和性能形成某種程度的限制。
當一個非工做者線程調度一個Task時,Task會添加到全局隊列。可是,每一個工做者線程都有它本身的本地隊列,上圖能夠看到,工做者線程是主,對應的本地隊列是附,當一個工做者線程調度一個Task時,Task會添加到調用線程的本地隊列,而不是全局隊列。
如今來看下工做者線程的描述:
工做者線程之因此稱爲Workers,它是名副其實的。它就是一「工做狂」,打個比方:
工做狂是什麼?作完本身的事還不夠,還要去搶別人的事作,別人的事作完了,就去找公共的事作,除非沒有事幹,要否則不會停下。
用這個比方,下面個人介紹就會淺顯不少了。
一個工做者線程準備處理一個工做項時,它老是先檢查它的本地隊列來查找一個Task。若是存在Task,工做者線程就從它的本地隊列中移除Task,並對工做項進行處理。
要注意的是,工做者線程是採用一個「棧」式結構,也就是後入先出(LIFO)算法,將任務從它的本隊隊列中取出。因爲工做者線程是惟一容許訪問本身的本地隊列頭的線程,因此不須要同步鎖,並且在隊列中添加和刪除任務的速度很是快,這個行爲的反作用就是,它的執行順序是相反的,後入的先執行。
還有哦,若是一個工做者線程發現本地隊列變空了,那麼它就會嘗試從另外一個工做者線程的本地隊列中「偷」一個Task,並獲取一個線程同步鎖,不過這種狀況仍是不多發生的。
再是,當全部本地隊列都爲空了,工做者線程就使用FIFO算法,從全局隊列中提取一個工做項,固然也會取得它的鎖。
如今全部隊列都爲空了,工做者線程就會本身進入睡眠狀態,等待事情的發生。若是睡眠了時間太長,它會本身醒來,並銷燬自身。
線程池會快速建立工做者線程,工做者線程的數量等於ThreadPool的SetMinThreads方法的值(默認是你的電腦CPU數),32位進程最多用32個CPU,64位進程最多可用64個CPU。而後建立工做者線程達到機器CPU數時,線程池會監視工做項的完成速度,若是工做項完成的時間太長,線程池就會建立更多的工做者線程,使工做加速完成。若是工做項的完成速度開始變快了,工做者線程就會被銷燬。
線程池的設計是很人性話的,有沒有體會到?
線程基礎用了這麼久才介紹完,新的起點又來啦。^_^