C#並發編程之概述


寫在前面

並發編程一直都存在,只不過過去的很長時間里,比較難以實現,隨着互聯網的發展,人口紅利的釋放,更加友好的支持並發編程已經成了主流編程語言的標配,而對於軟件開發人員來說,沒有玩過並發編程都會有點不好意思。本系列文章將會以C#語言為主,詳細介紹並發編程。

什么是並發編程,其實很簡單,並發編程就是在一台處理器上同時做多件事情,並發編程的目標就是充分利用處理器的每一個核,以達到最高的處理性能。舉個例子,服務器在響應第一個請求的同時響應第二個請求。

並發編程的方向

多線程

線程是一個獨立的運行單元,是操作系統中能夠進行運算調度的最小單位,它包含於進程之中,是進程中的實際運行單位。每個線程都有自己獨立的棧,但是與進程內的其他線程共享內存。現在的.NET程序都維護了一個線程池,里面有着一定數量的工作線程,這些線程等待着執行分配下來的任務,線程池也可以隨時監測線程的數量,以備開發者根據業務情況靈活處理。多線程也是我們並發編程的技術基礎。

並行編程

並行編程主要用於分解計算密集型的任務片段,並將其分配給多個線程。前提是,程序中的任務可以分割成多個相互獨立的任務塊,關鍵字是相互獨立,如果依賴太大,就不適合用並行編程。

並行編程利用CPU的空閑資源,充分提高了CPU的利用率,提高了系統的吞吐量。在大多數情況下,服務器本身就已經具備了並行處理能力,當通過編程進行並行處理的時候,需要慎重,因為使用不當將會導致內存溢出等風險,同時也會因為占用服務器資源而導致服務器本身的並行處理能力顯著下降,嚴重的時候回導致系統無法使用。所以在進行編程的時候,盡量不要處理過長或者過短的任務。

並行處理分為數據並行和任務並行,其實他們都使用到了動態調整的分割算法,在任務分割后分配給工作線程。可以通過以下兩種方式實現並行編程,一種是Parallel.ForEach以及更加優美的PLINQ,這是並行編程的推薦處理方式,並且它們自帶自動分配任務的算法,可以在運行時進行調整;

在編寫並行任務的時候,需要注意的是閉包所帶來的風險。因為閉包捕獲的是引用而不是值,所以可以在不經意間共享這些變量。一個比較好的處理就是,在使用閉包外的變量的時候,可以在閉包內定義局部變量,用以規避閉包帶來的變量共享問題。

需要說明的是,線程池會根據需要增加線程數量,線程池采用的是工作竊取隊列,以盡可能的達到高效

異步編程

目前最常用的異步編程模型是TAB編程(基於任務的編程模式)。異步編程提高了響應能力,也實現了可擴展性。比較直觀的是,大家在處理Winform的時候遇到過界面卡死的情況異步編程可以在程序運行的過程中繼續相應用戶的輸入,而不會導致界面卡死,並提高了提高服務器端應用的TPS(Transactions Per Second)和 QPS (Queries Per Second)。

.NET4.5以后為異步編程引入了async和await關鍵字,async關鍵字加在方法聲明上,主要用來配合方法內的await關鍵字,這兩個關鍵字的引入,使得C#在異步編程上更加優雅。如下所示

   1:  public async Task DelayAsync()
   2:  {
   3:      await Task.Delay(1000);
   4:  }

異步編程的執行流程一般是,當系統運行至await,會暫停,並可以捕捉到當前的上線文,SynchronizationContext,如果該上線文為空,就會使用當前的TaskScheduler,該方法也會在這個上線文中繼續執行。代碼執行完以后,會嘗試在原始的上下文中恢復運行。

注意:運行winform和asp.net請求時會采用UI上下文或者asp.net上下文,其他情況下則采用線程池上下文。

異步方法的等待方式有await,Task.Wait和Task<T>.Result。但是要避免是用Task.Wait和Task<T>.Result,因為他們在UI線程或者ASP.NET線程環境中會導致死鎖。這個地方需要說明一下死鎖問題

   1:  public async Task DelayAsync()
   2:  {
   3:      await Task.Delay(1000);//捕捉當前上下文,並試圖在已捕捉的上下文中繼續運行
   4:  }
   5:   
   6:  void Test()
   7:  {
   8:      Task task= DelayAsync();
   9:      Task.Wait();//同步程序塊,正在等待異步方法完成=======阻塞線程
  10:  }

UI或者asp.net的上下文每次只能同時運行一個線程。Wait方法已經阻塞了一個線程,所以在await的時候無法捕捉上下文。可以使用ConfigureAwait方法,設置參數continueOnCapturedContext為false。由此,可以帶來一個啟示,就是在線程池線程上使用ConfigureAwait(false),在用戶界面或接口代碼中再恢復過來。

異步編程中有一條重要的准則就是,當你使用了異步編程的時候,最好一直使用,也是為了防止死鎖。

優化使用:

避免上下文延續,延續任務過多會導致性能問題

如果一個async方法一個需要用到上下文一個不需要用到,可以考慮拆分為兩個async方法,這樣代碼組織也會更直觀。

寫到最后

以上只是提出了C#並發編程的引子,后面將會詳細介紹C#並發編程的知識點。當然,C#並發編程還有其他內容,比如響應式編程和TPL數據流這些,我平時用的比較少,所以此處沒有再做介紹,有興趣的同學可以另外查看一下。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM