不管您是爲具備單個處理器的計算機仍是爲具備多個處理器的計算機進行開發,您都但願應用程序爲用戶提供最好的響應性能,即便應用程序當前正在完成其餘工做。要使應用程序可以快速響應用戶操做,同時在用戶事件之間或者甚至在用戶事件期間利用處理器,最強大的方式之一是使用多線程技術。算法
多線程:線程是程序中一個單一的順序控制流程.在單個程序中同時運行多個線程完成不一樣的工做,稱爲多線程。若是某個線程進行一次長延遲操做, 處理器就切換到另外一個線程執行。這樣,多個線程的並行(併發)執行隱藏了長延遲,提升了處理器資源利用率,從而提升了總體性能。多線程是爲了同步完成多項任務,不是爲了提升運行效率,而是爲了提升資源使用效率來提升系統的效率編程
進程,是操做系統進行資源調度和分配的基本單位。是由進程控制塊、程序段、數據段三部分組成。一個進程能夠包含若干線程(Thread),線程能夠幫助應用程序同時作幾件事(比 如一個線程向磁盤寫入文件,另外一個則接收用戶的按鍵操做並及時作出反應,互相不干擾),在程序被運行後中,系統首先要作的就是爲該程序進程創建一個默認線程,而後程序可 以根據須要自行添加或刪除相關的線程。它是可併發執行的程序。在一個數據集合上的運行過程,是系統進行資源分配和調度的一個獨立單位,也是稱活動、路徑或任務,它有兩方面性質:活動性、併發性。進程能夠劃分爲運行、阻塞、就緒三種狀態,並隨必定條件而相互轉化:就緒--運行,運行--阻塞,阻塞--就緒。多線程
線程(thread),線程是CPU調度和執行的最小單位。有時被稱爲輕量級進程(Lightweight Process,LWP),是程序執行流的最小單元。一個標準的線程由線程ID,當前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程本身不擁有系統資源,只擁有一點在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的所有資源。一個線程能夠建立和撤消另外一個線程,同一進程中的多個線程之間能夠併發執行。因爲線程之間的相互制約,導致線程在運行中呈現出間斷性。線程也有就緒、阻塞和運行三種基本狀態。併發
主線程,進程建立時,默認建立一個線程,這個線程就是主線程。主線程是產生其餘子線程的線程,同時,主線程必須是最後一個結束執行的線程,它完成各類關閉其餘子線程的操做。儘管主線程是程序開始時自動建立的,它也能夠經過Thead類對象來控制,經過調用CurrentThread方法得到當前線程的引用函數
多線程的優點:進程有獨立的地址空間,同一進程內的線程共享進程的地址空間。啓動一個線程所花費的空間遠遠小於啓動一個進程所花費的空間,並且,線程間彼此切換所需的時間也遠遠小於進程間切換所須要的時間。高併發
一、提升應用程序響應。這對圖形界面的程序尤爲有意義,當一個操做耗時很長時,整個系統都會等待這個操做,此時程序不會響應鍵盤、鼠標、菜單的操做,而使用多線程技術,將耗時長的操做(time consuming)置於一個新的線程,能夠避免這種尷尬的狀況。
二、使多CPU系統更加有效。操做系統會保證當線程數不大於CPU數目時,不一樣的線程運行於不一樣的CPU上。
三、改善程序結構。一個既長又複雜的進程能夠考慮分爲多個線程,成爲幾個獨立或半獨立的運行部分,這樣的程序會利於理解和修改。。性能
多線程儘管優點明顯,可是線程併發衝突、同步以及管理跟蹤,可能給系統帶來不少不肯定性,這些必須引發足夠重視。 測試
廢話很少說開始咱們的多線程之旅。this
簡單總結了一下,通常有兩種狀況:spa
1)多個線程,完成同類任務,提升併發性能
2)一個任務有多個獨立的步驟,多個線程併發執行各子任務,提升任務處理效率
在咱們現實生活中,常常看到這樣的場景。有一堆貨物,有幾個搬運工負責將貨物搬運到指定地點。可是搬運工能力不一樣,有人一次能搬多箱,有人走路比較慢,搬運一趟的時間間隔比較長。搬運工,各自搬運,無前後,互不干擾。咱們如何在程序中實現這種場景呢?
案例分析:
這個就是最簡單的多線程的實際案例。每一個人至關於一個線程,併發執行。當貨物搬運完畢後,每一個線程自動中止。這裏暫時不考慮死鎖狀況。
案例代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace MutiThreadSample.Transport { /// <summary> /// 搬運工 /// </summary> public class Mover { /// <summary> /// 總數 /// </summary> public static int GoodsTotal { get; set; } /// <summary> /// 間隔時間 /// </summary> public static int IntervalTime { get; set; } /// <summary> /// 名稱 /// </summary> public string Name { get; set; } /// <summary> /// 單位時間搬運量 /// </summary> public int LaborAmount { get; set; } /// <summary> /// 搬運 /// </summary> public void Move() { while (GoodsTotal > 0) { GoodsTotal -= LaborAmount; Console.WriteLine("搬運者:{0} 於 {1} 搬運貨物 {2}",this.Name,DateTime.Now.Millisecond,this.LaborAmount); Thread.Sleep(IntervalTime); Console.WriteLine("搬運者:{0} Continue",this.Name); } } /// <summary> /// 搬運 /// </summary> /// <param name="interval">時間間隔</param> public void Move(object interval) { int tempInterval = 0; if (!int.TryParse(interval.ToString(), out tempInterval)) { tempInterval = IntervalTime; } while (GoodsTotal > 0) { GoodsTotal -= LaborAmount; Console.WriteLine("搬運者:{0} 於 {1} 搬運貨物 {2}", this.Name, DateTime.Now.Millisecond, this.LaborAmount); Thread.Sleep(tempInterval); } } } }
測試:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace MutiThreadSample.Transport { /// <summary> /// 測試搬運 /// </summary> public class TestMove { /// <summary> /// 搬運 /// </summary> public static void Move() { //測試搬運工 Mover.GoodsTotal = 200; Mover.IntervalTime = 10; Mover m1 = new Mover() { Name = "Tom", LaborAmount = 5 }; Mover m2 = new Mover() { Name = "Jim", LaborAmount = 10 }; Mover m3 = new Mover() { Name = "Lucy", LaborAmount = 20 }; List<Mover> movers = new List<Mover>(); movers.Add(m1); //movers.Add(m2); //movers.Add(m3); if (movers != null && movers.Count > 0) { foreach (Mover m in movers) { Thread thread = new Thread(new ThreadStart(m.Move)); thread.Start(); } } //Main Thread continue // validate Thread.Sleep() //int i =0; //int j = 0; //while (i < 10) //{ // while(j<10000000) // { // j++; // } // Console.WriteLine("CurrentThread:{0}", Thread.CurrentThread.Name); // i++; //} } /// <summary> /// 搬運 /// </summary> public static void MoveWithParamThread() { //測試搬運工 Mover.GoodsTotal = 1000; Mover.IntervalTime = 100; Mover m1 = new Mover() { Name = "Tom", LaborAmount = 5 }; Mover m2 = new Mover() { Name = "Jim", LaborAmount = 10 }; Mover m3 = new Mover() { Name = "Lucy", LaborAmount = 20 }; List<Mover> movers = new List<Mover>(); movers.Add(m1); movers.Add(m2); movers.Add(m3); if (movers != null && movers.Count > 0) { foreach (Mover m in movers) { Thread thread = new Thread(new ParameterizedThreadStart(m.Move)); thread.Start(10); } } } } }
經過案例咱們也接觸了Thread,下面咱們將詳細介紹Thread的功能。
建立並控制線程,設置其優先級並獲取其狀態。
Start()
致使操做系統將當前實例的狀態更改成 ThreadState.Running。
一旦線程處於 ThreadState.Running 狀態,操做系統就能夠安排其執行。 線程從方法的第一行(由提供給線程構造函數的 ThreadStart 或 ParameterizedThreadStart 委託表示)開始執行。線程一旦終止,它就沒法經過再次調用 Start 來從新啓動。
Thread.Sleep()
調用 Thread.Sleep 方法會致使當前線程當即阻止,阻止時間的長度等於傳遞給 Thread.Sleep 的毫秒數,這樣,就會將其時間片中剩餘的部分讓與另外一個線程。 一個線程不能針對另外一個線程調用 Thread.Sleep。
Interrupt()
中斷處於 WaitSleepJoin 線程狀態的線程。
Suspend和Resume(已過期)
掛起和繼續
在 .NET Framework 2.0 版中,Thread.Suspend 和 Thread.Resume 方法已標記爲過期,並將從將來版本中移除。
Abort()
方法用於永久地中止託管線程。一旦線程被停止,它將沒法從新啓動。
Join()
阻塞調用線程,直到某個線程終止時爲止。
ThreadPriority(優先級)
指定 Thread 的調度優先級。
ThreadPriority 定義一組線程優先級的全部可能值。線程優先級指定一個線程相對於另外一個線程的相對優先級。
每一個線程都有一個分配的優先級。在運行庫內建立的線程最初被分配 Normal 優先級,而在運行庫外建立的線程在進入運行庫時將保留其先前的優先級。能夠經過訪問線程的 Priority 屬性來獲取和設置其優先級。
根據線程的優先級調度線程的執行。用於肯定線程執行順序的調度算法隨操做系統的不一樣而不一樣。操做系統也能夠在用戶界面的焦點在前臺和後臺之間移動時動態地調整線程的優先級。
一個線程的優先級不影響該線程的狀態;該線程的狀態在操做系統能夠調度該線程以前必須爲 Running。
6、建立線程方式
經過搬運工案例咱們可以瞭解線程的工做原理,也明白了線程的建立方式。
其實在C#中建立線程有幾種方式,這裏給你們舉幾個經常使用例子,以下:
using System; using System.Threading; namespace MutiThreadSample { /// <summary> /// 建立線程的方式 /// </summary> class CreateThread { /// <summary> /// 不帶參數的委託 /// </summary> public void CreateThreadWithThreadStart() { Thread thread = new Thread(new ThreadStart(ThreadCallBack)); thread.Start(); } /// <summary> /// 帶參數的委託 /// </summary> public void CreateThreadWithParamThreadStart() { Thread thread = new Thread(new ParameterizedThreadStart(ThreadCallBackWithParam));
Object param = null; thread.Start(param); } /// <summary> /// 匿名函數 /// </summary> public void CreateThreadWithAnonymousFunction() { Thread thread = new Thread(delegate() { Console.WriteLine("進入子線程1"); for (int i = 1; i < 4; ++i) { Thread.Sleep(50); Console.WriteLine("\t+++++++子線程1+++++++++"); } Console.WriteLine("退出子線程1"); }); thread.Start(); } /// <summary> /// 直接賦值委託 /// </summary> public void CreateThreadWithCallBack() { Thread _hThread = new Thread(ThreadCallBack); _hThread.Start(); } /// <summary> /// 無參數的方法調用 /// </summary> public void ThreadCallBack() { // Do Something } /// <summary> /// 帶參數的方法 /// </summary> /// <param name="obj"></param> public void ThreadCallBackWithParam(object obj) { // Do Something } } }
時鐘線程
使用 TimerCallback 委託指定但願 Timer 執行的方法。 計時器委託在構造計時器時指定,而且不能更改。 此方法不在建立計時器的線程上執行,而是在系統提供的 ThreadPool 線程上執行。建立計時器時,能夠指定在第一次執行方法以前等待的時間量(截止時間)以及此後的執行期間等待的時間量(時間週期)。 可使用 Change 方法更改這些值或禁用計時器。
using System; using System.Threading; class TimerExample { static void Main() { // Create an event to signal the timeout count threshold in the // timer callback. AutoResetEvent autoEvent = new AutoResetEvent(false); StatusChecker statusChecker = new StatusChecker(10); // Create an inferred delegate that invokes methods for the timer. TimerCallback tcb = statusChecker.CheckStatus; // Create a timer that signals the delegate to invoke // CheckStatus after one second, and every 1/4 second // thereafter. Console.WriteLine("{0} Creating timer.\n", DateTime.Now.ToString("h:mm:ss.fff")); Timer stateTimer = new Timer(tcb, autoEvent, 1000, 250); // When autoEvent signals, change the period to every // 1/2 second. autoEvent.WaitOne(5000, false); stateTimer.Change(0, 500); Console.WriteLine("\nChanging period.\n"); // When autoEvent signals the second time, dispose of // the timer. autoEvent.WaitOne(5000, false); stateTimer.Dispose(); Console.WriteLine("\nDestroying timer."); } } class StatusChecker { private int invokeCount; private int maxCount; public StatusChecker(int count) { invokeCount = 0; maxCount = count; } // This method is called by the timer delegate. public void CheckStatus(Object stateInfo) { AutoResetEvent autoEvent = (AutoResetEvent)stateInfo; Console.WriteLine("{0} Checking status {1,2}.", DateTime.Now.ToString("h:mm:ss.fff"), (++invokeCount).ToString()); if(invokeCount == maxCount) { // Reset the counter and signal Main. invokeCount = 0; autoEvent.Set(); } } }
7、前臺線程和後臺線程
.Net的公用語言運行時(Common Language Runtime,CLR)能區分兩種不一樣類型的線程:前臺線程和後臺線程。這二者的區別就是:應用程序必須運行完全部的前臺線程才能夠退出;而對於後臺線程,應用程序則能夠不考慮其是否已經運行完畢而直接退出,全部的後臺線程在應用程序退出時都會自動結束。
一個線程是前臺線程仍是後臺線程可由它的IsBackground屬性來決定。這個屬性是可讀又可寫的。它的默認值爲false,即意味着一個線程默認爲前臺線程。
咱們能夠將它的IsBackground屬性設置爲true,從而使之成爲一個後臺線程。下面的例子是一個控制檯程序,程序一開始便啓動了10個線程,每一個線程運行5秒鐘時間。因爲線程的IsBackground屬性默認爲false,即它們都是前臺線程,因此儘管程序的主線程很快就運行結束了,但程序要到全部已啓動的線程都運行完畢纔會結束。示例代碼以下例子中的Test()所示
using System; using System.Threading; namespace MutiThreadSample.ThreadType { class ThreadTypeTest { /// <summary> /// 測試前臺線程 /// </summary> public static void Test() { for (int i = 0; i < 10; i++) { Thread thread = new Thread(new ThreadStart(ThreadFunc)); thread.Start(); } } /// <summary> /// 測試後臺線程 /// </summary> public static void TestBackgroundThread() { for (int i = 0; i < 10; i++) { Thread thread = new Thread(new ThreadStart(ThreadFunc)); thread.IsBackground = true; thread.Start(); } } public static void ThreadFunc() { Thread.Sleep(0); DateTime start = DateTime.Now; while ((DateTime.Now - start).Seconds < 20);//能夠停頓的時間長一點,效果更加明顯 } } }
接下來咱們對上面的代碼進行略微修改,將每一個線程的IsBackground屬性都設置爲true,則每一個線程都是後臺線程了。那麼只要程序的主線程結束了,整個程序也就結束了。示例代碼如代碼中的TestBackgroundThread()。
這個例子直接建立一個控制檯程序便可檢驗。
前臺和後臺線程的使用原則
既然前臺線程和後臺線程有這種差異,那麼咱們怎麼知道該如何設置一個線程的IsBackground屬性呢?下面是一些基本的原則:對於一些在後臺運行的線程,當程序結束時這些線程沒有必要繼續運行了,那麼這些線程就應該設置爲後臺線程。好比一個程序啓動了一個進行大量運算的線程,但是隻要程序一旦結束,那個線程就失去了繼續存在的意義,那麼那個線程就該是做爲後臺線程的。而對於一些服務於用戶界面的線程每每是要設置爲前臺線程的,由於即便程序的主線程結束了,其餘的用戶界面的線程極可能要繼續存在來顯示相關的信息,因此不能當即終止它們。這裏我只是給出了一些原則,具體到實際的運用每每須要編程者的進一步仔細斟酌。
這一章主要介紹多線程技術的基本知識。涉及多線程的具體應用,包括預防死鎖、線程同步、線程池等,在從此的文章會涉及到。