一、 進程# 簡單來說,進程是對資源的抽象,是資源的容器,在傳統操作系統中,進程是資源分配的基本單位,而且是執行的基本單位,進程支持并發執行,因為每個進程有獨立的數據,獨立的堆棧空間。一個程序想要并發執行,開多個進程即可。
Q1:在單核下,進程之間如何同時執行? 首先要區分兩個概念——并發和并行
并發:并發是指在一段微小的時間段中,有多個程序代碼段被CPU執行,宏觀上表現出來就是多個程序能”同時“執行。并行:并行是指在一個時間點,有多個程序段代碼被CPU執行,它才是真正的同時執行。 所以應該說進程之間是并發執行。對于CPU來講,它不知道進程的存在,CPU主要與寄存器打交道。有一些常用的寄存器,如程序計數器寄存器,這個寄存器存儲了将要執行的指令的地址,這個寄存器的地址指向哪,CPU就去哪。還有一些堆棧寄存器和通用寄存器等等等,總之,這些數據構成了一個程序的執行環境,這個執行環境就叫做”上下文(Context)“,進程之間切換本質就是保存這些數據到内存,術語叫做”保存現場“,然後恢複某個進程的執行環境,也即是”恢複現場“,整個過程術語叫做“上下文切換”,具體點就是進程上下文切換,這就是進程之間能并發執行的本質——頻繁的切換進程上下文。這個功能是由操作系統提供的,是内核态的,對應用軟件開發人員透明。
二、 線程# 進程雖然支持并發,但是對并發不是很友好,不友好是指每開啟一個進程,都要重新分配一部分資源,而線程相對進程來說,創建線程的代價比創建進程要小,所以引入線程能更好的提高并發性。在現代操作系統中,進程變成了資源分配的基本單位,而線程變成了執行的基本單位,每個線程都有獨立的堆棧空間,同一個進程的所有線程共享代碼段和地址空間等共享資源。相應的上下文切換從進程上下文切換變成了線程上下文切換。
三、 為什麼要引入進程和線程#提高CPU利用率,在早期的單道批處理系統中,如果執行中的代碼需要依賴與外部條件,将會導緻CPU空閑,例如文件讀取,等待鍵盤信号輸入,這将浪費大量的CPU時間。引入多進程和線程可以解決CPU利用率低這個問題。隔離程序之間的數據(每個進程都有單獨的地址空間),保證系統運行的穩定性。提高系統的響應性和交互能力。四、 在C#中創建托管線程# 補充:之所以叫托管線程是因為CLR需要屏蔽操作系統線程的細節,這樣可以更好的跨平台使用,所以一個托管線程不一定總在一個操作系統級别上的線程上執行,換言之一個托管線程可能在多個“本機線程上執行”,但是托管線程是一樣的,從托管線程的ID可知。
1. Thread類# 在.NET中,托管線程分為:
前台線程後台線程 一個.Net程序中,至少要有一個前台線程,所有前台線程結束了,所有的後台線程将會被公共語言運行時(CLR)強制銷毀,程序執行結束。
如下将在控制台程序中創建一個後台線程
1 static void Main(string[] args) 2 { 3 var t = new Thread(() = 4 { 5 Thread.Sleep(1000); 6 Console.WriteLine(執行完畢 7 }); 8 t.IsBackground = true; 9 t.Start(); 10 }
主線程(默認是前台線程)執行完畢,程序直接退出。
但IsBackground 屬性改為false時,控制台會打印“執行完畢”。
2. 有什麼問題# 直接使用Thread類來進行多線程編程浪費資源(服務器端更加明顯)且不方便,舉個例子。
假如我寫一個Web服務器程序,每個請求創建一個線程,那麼每一次我都要new一個Thread對象,然後傳入處理HttpRequest的委托,處理完之後,線程将會被銷毀,這将會導緻浪費大量CPU時間和内存,在早期CPU性能不行和内存資源珍貴的情況下這個缺點會被放大,在現在這個缺點不是很明顯,原因是硬件上來了。
不方便體現在哪呢?
無法直接獲取另一個線程内未被捕捉的異常無法直接獲取線程函數的返回值 1 public static void ThrowException() 2 { 3 throw new Exception(發生異常 4 } 5 static void Main(string[] args) 6 { 7 var t = new Thread(() = 8 { 9 Thread.Sleep(1000); 10 ThrowException(); 11 }); 12 t.IsBackground = false; 13 try 14 { 15 t.Start(); 16 } 17 catch(Exception e) 18 { 19 Console.WriteLine(e.Message); 20 } 21 }
上述代碼将會導緻程序崩潰,如下圖。
要想直接獲取返回值和可以直接從主線程捕捉線程函數内未捕捉的異常,我們可以這麼做。
新建一個MyTask.cs文件,内容如下
1 using System; 2 using System.Threading; 3 namespace ConsoleApp1 4 { 5 public class MyTask 6 { 7 private Thread _thread; 8 private Action _action; 9 private Exception _innerException; 10 public MyTask() 11 { 12 } 13 public MyTask(Action action) 14 { 15 _action = action; 16 } 17 protected virtual void Excute() 18 { 19 try 20 { 21 _action(); 22 } 23 catch(Exception e) 24 { 25 _innerException = e; 26 } 27 28 } 29 public void Start() 30 { 31 if (_thread != null) throw new InvalidOperationException(任務已經開始 32 _thread = new Thread(() = Excute()); 33 _thread.Start(); 34 } 35 public void Start(Action action) 36 { 37 _action = action; 38 if (_thread != null) throw new InvalidOperationException(任務已經開始 39 _thread = new Thread(() = Excute()); 40 _thread.Start(); 41 } 42 public void Wait() 43 { 44 _thread.Join(); 45 if (_innerException != null) throw _innerException; 46 } 47 } 48 public class MyTask : MyTask 49 { 50 private Func _func { get; } 51 private T _result; 52 public T Result { 53 54 private set = _result = value; 55 get 56 { 57 base.Wait(); 58 return _result; 59 } 60 } 61 public MyTask(Func func) 62 { 63 _func = func; 64 } 65 public new void Start() 66 { 67 base.Start(() = 68 { 69 Result = _func(); 70 }); 71 } 72 } 73 }
簡單地包裝了一下(不要在意細節),我們便可以實現我們想要的效果。
測試代碼如下
1 public static void ThrowException() 2 { 3 throw new Exception(發生異常 4 } 5 public static void Test3() 6 { 7 MyTaskstring myTask = new MyTaskstring(() = 8 { 9 Thread.Sleep(1000); 10 return 執行完畢 11 }); 12 myTask.Start(); 13 try 14 { 15 Console.WriteLine(myTask.Result); 16 } 17 catch (Exception e) 18 { 19 Console.WriteLine(e.Message); 20 } 21 } 22 public static void Test2() 23 { 24 MyTaskstring myTask = new MyTaskstring(() = 25 { 26 Thread.Sleep(1000); 27 ThrowException(); 28 return 執行完畢 29 }); 30 myTask.Start(); 31 try 32 { 33 Console.WriteLine(myTask.Result); 34 } 35 catch(Exception e) 36 { 37 Console.WriteLine(e.Message); 38 } 39 } 40 public static void Test1() 41 { 42 MyTask myTask = new MyTask(() = 43 { 44 Thread.Sleep(1000); 45 ThrowException(); 46 }); 47 myTask.Start(); 48 try 49 { 50 myTask.Wait(); 51 } 52 catch (Exception e) 53 { 54 Console.WriteLine(e.Message); 55 } 56 } 57 static void Main(string[] args) 58 { 59 Test1(); 60 Test2(); 61 Test3(); 62 }
可以看到,我們可以通過簡單包裝Thread對象,便可實現如下效果
直接讀取線程函數返回值直接捕捉線程函數未捕捉的異常(前提是調用了Wait()函數或者Result屬性) 這是理解和運用Task的基礎,Task功能非常完善,但是運用好Task需要掌握許多概念,下面再說。
作者: 白煙染黑墨
出處:htt
更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!