我第一次接觸“線程”的概念時,覺得它深奧難懂,看了好多本書,花了很長時間才領悟到它的真諦。現在我就以一個初學者的心態,把我所理解的“多線程”描述給大家。這一次是系列文章,比較完整的展示與線程相關的基本概念。希望對初學者有所幫助。 如果你是高手,請你別繼續看,會浪費你寶貴的時間。 一、基本概念 什麼是 ...
我第一次接觸“線程”的概念時,覺得它深奧難懂,看了好多本書,花了很長時間才領悟到它的真諦。現在我就以一個初學者的心態,把我所理解的“多線程”描述給大家。這一次是系列文章,比較完整的展示與線程相關的基本概念。希望對初學者有所幫助。
如果你是高手,請你別繼續看,會浪費你寶貴的時間。
一、基本概念
什麼是進程?
當一個程式開始運行時,它就是一個進程,進程包括運行中的程式和程式所使用到的記憶體和系統資源。 而一個進程又是由多個線程所組成的。
什麼是線程?
線程是程式中的一個執行流,每個線程都有自己的專有寄存器(棧指針、程式計數器等),但代碼區是共用的,即不同的線程可以執行同樣的函數。
什麼是多線程?
多線程是指程式中包含多個執行流,即在一個程式中可以同時運行多個不同的線程來執行不同的任務,也就是說允許單個程式創建多個並行執行的線程來完成各自的任務。
前臺線程後臺線程?
應用程式的主線程和通過構造一個Thread對象來顯式創建的任何線程都預設是前臺線程。相反線程池線程預設為後臺線程。另外由進入托管執行環境的本機代碼創建的任何線程都被標記為後臺線程。
線上程的生命周期中,任何時候都可以從前臺變為後臺,或者從後臺變為前臺。
前臺線程能阻止應用程式的終結。一直到所有的前臺線程終止後,CLR才能關閉應用程式(即卸載承載的應用程式域)。
後臺線程(有時也叫守護線程)被CLR認為是程式執行中可做出犧牲的途徑,即在任何時候(即使這個線程此時正在執行某項工作)都可能被忽略。因此,如果所有的前臺線程終止,當應用程式域卸載時,所以的後臺線程也會被自動終止。
線程是輕量級進程。一個使用線程的常見實例是現代操作系統中並行編程的實現。使用線程節省了 CPU 周期的浪費,同時提高了應用程式的效率。
二、線程的生命周期
線程生命周期開始於 System.Threading.Thread 類的對象被創建時,結束於線程被終止或完成執行時。
線程生命周期中的各種狀態:
未啟動狀態:當線程實例被創建但 Start 方法未被調用時的狀況(將該線程標記為可以運行的狀態,但具體執行時間由cpu決定。)。
就緒狀態:當線程準備好運行並等待 CPU 周期時的狀況。
不可運行狀態:下麵的幾種情況下線程是不可運行的:(已經調用 Sleep 方法,已經調用 Wait 方法,通過 I/O 操作阻塞)
死亡狀態:當線程已完成執行或已中止時的狀況。
三、線程
1、主線程
進程中第一個被執行的線程稱為主線程
using System; using System.Threading; namespace Threading { class Program { static void Main(string[] args) { Thread th = Thread.CurrentThread; th.Name = "MainThread"; Console.WriteLine("This is {0}", th.Name); Console.ReadKey(); } } }
輸出:This is MainThread
2、線程的創建
using System; using System.Threading; namespace Threading { class Program { public static void Thread1() { Console.WriteLine("Thread1 starts"); } public static void Thread2(object data) { Console.WriteLine("Thread2 starts,para:{0}", data.ToString()); } static void Main(string[] args) { var t1 = new Thread(Thread1); t1.Start(); var t2 = new Thread(Thread2); t2.Start("thread2"); Console.ReadKey(); } } }
輸入:
Thread1 starts
Thread2 starts,para:thread2
3、線程的管理
sleep()掛起和Abort() 銷毀線程
通過拋出 threadabortexception 在運行時中止線程。這個異常不能被捕獲,如果有 finally 塊,控制會被送至 finally 塊
using System; using System.Threading; namespace Threading { class Program { public static void Thread1() { Console.WriteLine("Thread1 starts"); Console.WriteLine("Thread1 Paused for 5 seconds"); Thread.Sleep(5000); Console.WriteLine("Thread1 resumes"); } static void Main(string[] args) { var t1 = new Thread(Thread1); t1.Start(); Console.ReadKey(); } } }線程掛起代碼
using System; using System.Threading; namespace Threading { class Program { public static void Thread1() { try { Console.WriteLine("Thread1 starts"); for (int i = 0; i <= 10; i++) { Thread.Sleep(500); Console.WriteLine(i); } Console.WriteLine("Thread1 Completed"); } catch (ThreadAbortException ex) { Console.WriteLine("Thread1 Abort Exception"); } finally { Console.WriteLine("Couldn't catch the Thread1 Exception"); } } static void Main(string[] args) { //開啟子線程 var t1 = new Thread(Thread1); t1.Start(); //主線程掛起2s Thread.Sleep(2000); //終止t1子線程 t1.Abort(); Console.ReadKey(); } } }線程銷毀代碼
銷毀代碼執行結果:
四、線程池
在多線程程式中,線程把大部分的時間花費在等待狀態,等待某個事件發生,然後才能給予響應我們一般用ThreadPool(線程池)來解決;線程平時都處於休眠狀態,只是周期性地被喚醒我們使用使用Timer(定時器)來解決。
由於線程的創建和銷毀需要耗費一定的開銷,過多的使用線程會造成記憶體資源的浪費,出於對性能的考慮,於是引入了線程池的概念。線程池維護一個請求隊列,線程池的代碼從隊列提取任務,然後委派給線程池的一個線程執行,線程執行完不會被立即銷毀,這樣既可以在後臺執行任務,又可以減少線程創建和銷毀所帶來的開銷。線程池線程預設為後臺線程。
線程池自動管理線程線程的創建和銷毀。
代碼展示:
using System; using System.Threading; namespace Threading { class Program { public static void Thread1(object data) { Console.WriteLine("Thread1 => {0}",data.ToString()); } static void Main(string[] args) { //控制線程數大小 //第一個參數是:線程池中輔助線程的最大數目 //第二個參數是:線程池中非同步 I/O 線程的最大數目 ThreadPool.SetMaxThreads(3, 3); for (int i = 0; i < 10; i++) { //ThreadPool是靜態類無需實例化, //ThreadPool.QueueUserWorkItem(new WaitCallback(Thread1), i); ThreadPool.QueueUserWorkItem(Thread1, i); } Console.WriteLine("Thread1 sleep"); Thread.Sleep(100000); Console.WriteLine("Thread1 end"); Console.ReadKey(); } } }
運行結果:
但是為什麼最開始輸出Thread1 sleep?有時候也會在中間隨機輸出呢?
其實,線程池的啟動和終止不是我們程式所能控制的,線程池中的線程執行完之後是沒有返回值的,我們可以用ManualResetEvent通知一個或多個正在等待的線程已發生事件
修改後的代碼:
using System; using System.Threading; namespace Threading { class Program { //新建ManualResetEvent對象並且初始化為無信號狀態 private static ManualResetEvent mre = new ManualResetEvent(false); public static void Thread1(object data) { Console.WriteLine("Thread1 => {0}",data.ToString()); if (Convert.ToInt32(data) == 9) { mre.Set(); } } static void Main(string[] args) { //控制線程數大小 //第一個參數是:線程池中輔助線程的最大數目 //第二個參數是:線程池中非同步 I/O 線程的最大數目 ThreadPool.SetMaxThreads(3, 3); for (int i = 0; i < 10; i++) { //ThreadPool是靜態類無需實例化, //ThreadPool.QueueUserWorkItem(new WaitCallback(Thread1), i); ThreadPool.QueueUserWorkItem(Thread1, i); } //阻止當前線程,直到當前 WaitHandle 收到信號為止。 mre.WaitOne(Timeout.Infinite, true); Console.WriteLine("Thread1 sleep"); Thread.Sleep(100000); Console.WriteLine("Thread1 end"); Console.ReadKey(); } } }
輸入結果:
ok,搞定。
參考資料:
ThreadPool:https://msdn.microsoft.com/zh-cn/library/system.threading.threadpool.aspx#Y0
ManualResetEvent:https://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent.aspx
五、總結
多線程的好處:
可以提高CPU的利用率。在多線程程式中,一個線程必須等待的時候,CPU可以運行其它的線程而不是等待,這樣就大大提高了程式的效率。
多線程的不利方面:
線程也是程式,所以線程需要占用記憶體,線程越多占用記憶體也越多;
多線程需要協調和管理,所以需要CPU時間跟蹤線程;
線程之間對共用資源的訪問會相互影響,必須解決競用共用資源的問題;
線程太多會導致控制太複雜,最終可能造成很多Bug;
六、加關註
如果本文對你有幫助,請點擊右下角【好文要頂】和【關註我】