無論您是為具有單個處理器的計算機還是為具有多個處理器的計算機進行開發,您都希望應用程序為 用戶提供最好的響應性能,即使應用程序當前正在完成其他工作。要使應用程序能夠快速響應用戶操作 ,同時在用戶事件之間或者甚至在用戶事件期間利用處理器,最強大的方式之一是使用多線程技術。
多線程:線程是程序中一個單一的順序控制流程.在單個程序中同時運行多個線程完成不同的工 作,稱為多線程。如果某個線程進行一次長延遲操作, 處理器就切換到另一個線程執行。這樣,多個線 程的並行(並發)執行隱藏了長延遲,提高了處理器資源利用率,從而提高了整體性能。多線程是為了同 步完成多項任務,不是為了提高運行效率,而是為了提高資源使用效率來提高系統的效率
一、進 程與線程
進程,是操作系統進行資源調度和分配的基本單位。是由進程控制塊、程序段、數據段 三部分組成。一個進程可以包含若干線程(Thread),線程可以幫助應用程序同時做幾件事(比 如一個線 程向磁盤寫入文件,另一個則接收用戶的按鍵操作並及時做出反應,互相不干擾),在程序被運行後中, 系統首先要做的就是為該程序進程建立一個默認線程,然後程序可 以根據需要自行添加或刪除相關的線 程。它是可並發執行的程序。在一個數據集合上的運行過程,是系統進行資源分配和調度的一個獨立單 位,也是稱活動、路徑或任務,它有兩方面性質:活動性、並發性。進程可以劃分為運行、阻塞、就緒 三種狀態,並隨一定條件而相互轉化:就緒--運行,運行--阻塞,阻塞--就緒。
線程(thread), 線程是CPU調度和執行的最小單位。有時被稱為輕量級進程(Lightweight Process,LWP),是程序執行流 的最小單元。一個標准的線程由線程ID,當前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進 程中的一個實體,是被系統獨立調度和分派的基本單位,線程自己不擁有系統資源,只擁有一點在運行 中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源。一個線程可以創建 和撤消另一個線程,同一進程中的多個線程之間可以並發執行。由於線程之間的相互制約,致使線程在 運行中呈現出間斷性。線程也有就緒、阻塞和運行三種基本狀態。
主線程,進程創建時,默認創 建一個線程,這個線程就是主線程。主線程是產生其他子線程的線程,同時,主線程必須是最後一個結 束執行的線程,它完成各種關閉其他子線程的操作。盡管主線程是程序開始時自動創建的,它也可以通 過Thead類對象來控制,通過調用CurrentThread方法獲得當前線程的引用
多線程的優勢:進程有 獨立的地址空間,同一進程內的線程共享進程的地址空間。啟動一個線程所花費的空間遠遠小於啟動一 個進程所花費的空間,而且,線程間彼此切換所需的時間也遠遠小於進程間切換所需要的時間。
二、多線程優點
1、提高應用程序響應。這對圖形界面的程序尤其有意義,當一個操作耗時很長 時,整個系統都會等待這個操作,此時程序不會響應鍵盤、鼠標、菜單的操作,而使用多線程技術,將 耗時長的操作(time consuming)置於一個新的線程,可以避免這種尴尬的情況。
2、使多CPU系統更 加有效。操作系統會保證當線程數不大於CPU數目時,不同的線程運行於不同的CPU上。
3、改善程序 結構。一個既長又復雜的進程可以考慮分為多個線程,成為幾個獨立或半獨立的運行部分,這樣的程序 會利於理解和修改。。
多線程盡管優勢明顯,但是線程並發沖突、同步以及管理跟蹤,可能給系 統帶來很多不確定性,這些必須引起足夠重視。
廢話不多說開始我們的多線程之旅。
三 、多線程的應用場合:
簡單總結了一下,一般有兩種情況:
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的功能。
五、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。
六、創建線程方式
通過搬運工案例我們能夠了解線程的工作原理,也明白了線 程的創建方式。
其實在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)); thread.Start(); } /// <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(); } } }
查看本欄目
七、前台線程和後台線程
.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屬性呢?下面是一些基本的原則:對於一些在後台運行的線程,當程序結束時這些線程沒 有必要繼續運行了,那麼這些線程就應該設置為後台線程。比如一個程序啟動了一個進行大量運算的線 程,可是只要程序一旦結束,那個線程就失去了繼續存在的意義,那麼那個線程就該是作為後台線程的 。而對於一些服務於用戶界面的線程往往是要設置為前台線程的,因為即使程序的主線程結束了,其他 的用戶界面的線程很可能要繼續存在來顯示相關的信息,所以不能立即終止它們。這裡我只是給出了一 些原則,具體到實際的運用往往需要編程者的進一步仔細斟酌。
八、總結
這一章主要介紹多 線程技術的基本知識。涉及多線程的具體應用,包括預防死鎖、線程同步、線程池等,在今後的文章會 涉及到。