線程中的概念不少,若是沒有代碼示例來理解,會比較晦澀,並且有些概念落不到實處,所以,本文以一些運行示例代碼,結果來闡述線程中的一些基礎概念。讓本身跟讀者一塊兒把線程中的概念理解地更深入。html
class ThreadTest2 { bool done; static void Main() { ThreadTest2 tt = new ThreadTest2(); // Create a common instance new Thread(tt.Go).Start(); tt.Go(); } // Note that Go is now an instance method void Go() { if (!done) { done = true; Console.WriteLine("Done"); } } }
運行結果以下:git
Done
class ThreadTest2 { bool done; static void Main() { ThreadTest2 tt = new ThreadTest2(); // Create a common instance new Thread(tt.Go).Start(); tt.Go(); } // Note that Go is now an instance method void Go() { if (!done) { Console.WriteLine("Done"); done = true; } } }
運行結果以下:github
Done Done
線程搶佔例子2:算法
for (int i = 0; i < 10; i++) new Thread (() => Console.Write (i)).Start();
運行結果
0223557799
編程
class ThreadTest2 { static readonly object locker = new object(); bool done; static void Main() { ThreadTest2 tt = new ThreadTest2(); // Create a common instance new Thread(tt.Go).Start(); tt.Go(); } // Note that Go is now an instance method void Go() { lock (locker) { if (!done) { Console.WriteLine("Done"); done = true; } } } }
運行結果以下:緩存
Done
class Program { static void Main() { Thread t = new Thread(Go); t.Start(); t.Join(); Console.WriteLine("Thread t has ended!"); } static void Go() { for (int i = 0; i < 1000; i++) Console.Write("y"); } }
運行結果:安全
1000個y打印完畢才輸出"Thread t has ended!"。服務器
Thread.Sleep (500);
也會阻塞線程,讓渡CPU的執行權給其餘線程。網絡
sleep(0)效果至關於yield(),會讓當前線程放棄剩餘時間片,進入相同優先級線程隊列的隊尾,只有排在前面的全部同優先級線程完成調度後,它才能再次獲執行的機會。多線程
多線痛經過內部的線程調度器(thread scheduler)管理,經過clr委託操做系統。線程調度器會分配適當的執行時間給活動線程,線程等待(鎖)或者線程阻塞(用戶輸入)不會消耗cpu執行時間。
單核處理器電腦上,在Windows,時間片一般會被分配幾十毫秒,遠大於線程上下文切換還時間幾毫秒。
在多處理器計算機上,多線程是經過時間片和真正的併發實現的,其中不一樣的線程在不一樣的CPU上同時運行代碼。 幾乎能夠確定,因爲操做系統須要服務本身的線程以及其餘應用程序的線程,所以還會有必定的時間片。
當線程的執行因爲諸如時間片之類的外部因素而被中斷時,該線程被認爲是被搶佔的。 在大多數狀況下,線程沒法控制其被搶佔的時間和地點。
線程與進程有類似之處。 就像進程在計算機上並行運行同樣,多個線程在單個進程中並行運行。 進程彼此徹底隔離; 線程的隔離度有限。 特別是,線程與在同一應用程序中運行的其餘線程共享(堆)內存。 這就是爲何線程有用的緣由:例如,一個線程能夠在後臺獲取數據,而另外一個線程能夠在數據到達時顯示數據。
利於響應式用戶界面
在同時並行運行的「worker」線程上運行耗時的任務,主UI線程能夠自由繼續處理鍵盤和鼠標事件。
有效利用本來被阻塞的CPU
當線程正在等待來自另外一臺計算機或硬件的響應時,多線程頗有用。 當一個線程在執行任務時被阻塞時,其餘線程能夠利用原本沒有負擔的計算機的其餘線程來響應任務。
並行編程
若是以``分而治之''策略在多個線程之間共享工做負載,則執行密集計算的代碼能夠在多核或多處理器計算機上更快地執行(請參閱第5部分)。
隨機執行
在多核計算機上,有時能夠經過預測可能須要完成的事情而後提早進行來提升性能。 LINQPad使用此技術來加速新查詢的建立。 一種變化是並行運行許多不一樣的算法,這些算法均可以解決同一任務。 誰先得到「勝利」,當您不知道哪一種算法執行速度最快時,此方法將很是有效。
容許服務同時處理請求
在服務器上,客戶端請求能夠同時到達,所以須要並行處理(若是使用ASP.NET,WCF,Web服務或遠程處理,.NET Framework會爲此自動建立線程)。 這在客戶端上也頗有用(例如,處理對等網絡-甚至來自用戶的多個請求)。
使用ASP.NET和WCF之類的技術,您若是不知道多線程正在發生-除非您在沒有適當鎖定的狀況下訪問共享數據(可能經過靜態字段),會破壞線程安全性。
線程之間的交互(一般是經過共享數據),會帶來不少複雜性,但卻不可避免,所以,有必要將交互保持在最低限度,並儘量地堅持簡單可靠的設計。
好的策略是將多線程邏輯封裝到可重用的類中,這些類能夠獨立檢查和測試。 框架自己提供了許多更高級別的線程結構,咱們將在後面介紹。
線程化還會在調度和切換線程時(若是活動線程多於CPU內核)會致使資源和CPU的浪費,而且還會產生建立/釋放成本。 多線程並不老是能夠加快您的應用程序的速度-若是使用過多或使用不當,它甚至可能減慢其速度。 例如,當涉及大量磁盤I / O時,讓幾個工做線程按順序運行任務比一次執行10個線程快得多。
最方便的方法就是經過lambda表達式調用匿名方法,傳參數。
static void Main() { Thread t = new Thread(() =>Print("Hello from t!")); t.Start(); } static void Print(string message) { Console.WriteLine(message); }
static void Main() { Thread t = new Thread(Print); t.Start("Hello from t!"); } static void Print(object messageObj) { string message = (string)messageObj; // We need to cast here Console.WriteLine(message); }
string text = "t1"; Thread t1 = new Thread ( () => Console.WriteLine (text) ); text = "t2"; Thread t2 = new Thread ( () => Console.WriteLine (text) ); t1.Start(); t2.Start();
運行結果:
t2 t2
以上運行結果說明,在t1線程建立以前text被修改爲了t2。
每一個線程都有名稱屬性,目的是爲了更方便調試。
static void Main() { Thread.CurrentThread.Name = "main"; Thread worker = new Thread(Go); worker.Name = "worker"; worker.Start(); Go(); } static void Go() { Console.WriteLine("Hello from " + Thread.CurrentThread.Name); }
運行結果:
Hello from main Hello from worker
Thread worker = new Thread(() => Console.ReadLine()); if (args.Length > 0) worker.IsBackground = true; worker.Name = "backThread"; worker.Start(); Console.WriteLine("finish!");
前臺線程會隨着主線程窗口關閉而中止,後臺線程及時主線程窗口關閉本身獨立運行。
線程優先級決定了操做系統執行活動線程時間的長短。
enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest }
有時候提升了線程的優先級,但卻仍然沒法知足一些實時的應用需求,這時候就須要提升進程的優先級,System.Diagnostics命名空間中的process進程類.
using (Process p = Process.GetCurrentProcess()) p.PriorityClass = ProcessPriorityClass.High;
實際上,ProcessPriorityClass.High比最高優先級低1個級別:Realtime。 將進程優先級設置爲Realtime,可指示OS,您永遠不但願該進程將CPU時間浪費在另外一個進程上。 若是您的程序進入意外的無限循環,您甚至可能會發現操做系統已鎖定,只剩下電源按鈕能夠拯救您! 所以,高一般是實時應用程序的最佳選擇。
若是您的實時應用程序具備用戶界面,則提升處理優先級將給屏幕更新帶來過多的CPU時間,從而減慢整個計算機的速度(尤爲是在UI複雜的狀況下)。 下降主線程的優先級並提升進程的優先級可確保實時線程不會因屏幕重繪而被搶佔,但不會解決使其餘應用程序耗盡CPU時間的問題,由於操做系統仍會分配 整個過程的資源不成比例。 理想的解決方案是使實時工做程序和用戶界面做爲具備不一樣進程優先級的單獨應用程序運行,並經過遠程處理或內存映射文件進行通訊。 內存映射文件很是適合此任務。 咱們將在C#4.0的第14和25章中簡要介紹它們的工做原理。
Go沒法補捉異常,GoCatch能捕獲當前線程的異常,輸出Console.WriteLine("exception.");因而可知,線程建立以後,異常只能由本線程捕獲,若是其調用方須要捕獲,則得用共享內存方式往上傳,Task幫咱們作了這件事,調用方可在task.result裏捕獲到其餘線程的異常。
public static void Main() { try { new Thread(Go).Start(); Console.ReadKey(); } catch (Exception ex) { // We'll never get here! Console.WriteLine("Exception!"); } } static void Go() { throw null; } // Throws a NullReferenceException static void GoCatch() { try { // ... throw null; // The NullReferenceException will get caught below // ... } catch (Exception ex) { // Typically log the exception, and/or signal another thread // that we've come unstuck // ... Console.WriteLine("exception."); } }
當你建立一個線程,幾百毫秒會被花費在例如建立本地私有變量堆棧。每一個線程都會默認消耗1MB內存,從而容許在很是精細的級別上應用多線程而不會影響性能。當利用多核處理器以「分而治之」的方式並行執行計算密集型代碼時,這頗有用。
線程池還限制了將同時運行的工做線程總數。活動線程過多會限制操做系統的管理負擔,並使CPU緩存無效。一旦達到限制,做業將排隊並僅在另外一個做業完成時纔開始。這使任意併發的應用程序成爲可能,例如Web服務器。 (異步方法模式是一種先進的技術,經過高效利用池線程來進一步實現這一點;咱們在C#4.0的第23章中簡要介紹了這一點)。
有多種進入線程池的方法:
•經過Task Parallel Library(來自Framework 4.0)
•經過調用ThreadPool.QueueUserWorkItem
•經過異步委託(await)
•經過BackgroundWorker
如下方法間接使用線程池:
•WCF,遠程,ASP.NET和ASMX Web服務應用程序服務器
•System.Timers.Timer和System.Threading.Timer
•以Async結尾的框架方法,例如WebClient(基於事件的異步模式)上的框架方法和大多數BeginXXX方法(異步編程模型模式)
•PLINQ
使用池線程時,須要注意如下幾點:
•您沒法設置池線程的名稱,這會使調試更加困難(儘管您能夠在Visual Studio的「線程」窗口中進行調試時附加說明)。
•池線程始終是後臺線程(這一般不是問題)。
•除非您調用ThreadPool.SetMinThreads(請參閱優化線程池),不然阻塞線程池可能會在應用程序的早期階段觸發額外的延遲。
您能夠自由更改池線程的優先級-將其釋放回池後將恢復爲正常狀態。
您能夠經過Thread.CurrentThread.IsThreadPoolThread屬性查詢當前是否在線程池上執行。
經過Task Parallel Library庫中的Task類可輕鬆使用線程池,Task類由Framework 4.0引入,若是你熟悉老的結構,考慮用不帶泛型Task類來替代ThreadPool.QueueUserWorkItem,而泛型Task
使用不帶泛型例子的Task類,調用Task.Factory.StartNew,傳遞一個目標方法的委託;
static void Main() // The Task class is in System.Threading.Tasks { var task=Task.Factory.StartNew(Go); Console.WriteLine("main"); task.Wait() ; Console.WriteLine(task.Result); Console.ReadLine(); } static string Go() { if (Thread.CurrentThread.IsThreadPoolThread) { Console.WriteLine("Hello from the thread pool!"); } else { Console.WriteLine("Hello just from the thread!"); } return "task complete!"; }
輸出結果:
main Hello from the thread pool! task complete!
static void Main() // The Task class is in System.Threading.Tasks { var task=Task.Factory.StartNew(Go); Console.WriteLine("main"); try { task.Wait(); } catch (Exception e) { Console.WriteLine("exception!"); } Console.WriteLine(task.Result); Console.ReadLine(); } static string Go() { if (Thread.CurrentThread.IsThreadPoolThread) { Console.WriteLine("Hello from the thread pool!"); } else { Console.WriteLine("Hello just from the thread!"); } throw null; return "task complete!"; }
運行結果,在主線程中捕獲到了其餘線程的異常:
static void Main() { // Start the task executing: Task<string> task = Task.Factory.StartNew<string> ( () => DownloadString ("http://www.linqpad.net") ); // We can do other work here and it will execute in parallel: RunSomeOtherMethod(); // When we need the task's return value, we query its Result property: // If it's still executing, the current thread will now block (wait) // until the task finishes: string result = task.Result; } static string DownloadString (string uri) { using (var wc = new System.Net.WebClient()) return wc.DownloadString (uri); }
Task<string> 就是一個返回值爲string的異步委託。
若是你的框架是.Net 4.0以前的,你能夠不經過Task Parallel Library 進入線程池。
static void Main() { ThreadPool.QueueUserWorkItem(Go); ThreadPool.QueueUserWorkItem(Go, 123); Console.ReadLine(); } static void Go(object data) // data will be null with the first call. { Console.WriteLine("Hello from the thread pool! " + data); }
運行結果:
Hello from the thread pool! Hello from the thread pool! 123
與Task不一樣:
即鄙人寫的這篇文章深刻理解C#中的異步(一)——APM模式EAP模式裏的2.1APM異步編程模式。
須要補充說明的是:
委託的EndInvoke 作了3件事:
線程池從其池中的一個線程開始。 分配任務後,池管理器會「注入」新線程以應對額外的併發工做負載(最大限制)。 在足夠長時間的不活動以後,若是池管理器懷疑這樣作會致使更好的吞吐量,則能夠「退出」線程。
您能夠經過調用ThreadPool.SetMaxThreads;來設置池將建立的線程的上限; 默認值爲:
•32位環境中的Framework 4.0中的1023
•64位環境中的Framework 4.0中的32768
•Framework 3.5中的每一個內核250個
•Framework 2.0中每一個內核25個
您還能夠經過調用ThreadPool.SetMinThreads設置下限。 下限的做用是微妙的:這是一種高級優化技術,它指示池管理器在達到下限以前不要延遲線程的分配。 當存在阻塞的線程時,提升最小線程數可提升併發性。
默認的下限是每一個處理器內核一個線程-容許所有CPU利用率的最小值。 可是,在服務器環境(例如IIS下的ASP .NET)上,下限一般要高得多-多達50個或更多。
設置線程池最小線程數量。
ThreadPool.SetMinThreads (50, 50);
版權聲明:本文爲博主翻譯文章+本身理解,部分代碼本身寫,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接和本聲明。 本文連接:http://www.javashuo.com/article/p-ysogmvjr-ny.html