細說併發編程-TPL

来源:https://www.cnblogs.com/lucky_hu/archive/2019/03/21/10569564.html
-Advertisement-
Play Games

本節導航 基本概念 併發編程 TPL 線程基礎 windows為什麼要支持線程 線程開銷 CPU的發展 使用線程的理由 如何寫一個簡單Parallel.For迴圈 數據並行 Parallel.For剖析   優秀軟體的一個關鍵特征就是具有併發性。過去的幾十年,我們可以進行併發編程 ...



本節導航

  • 基本概念
    • 併發編程
    • TPL
  • 線程基礎
    • windows為什麼要支持線程
    • 線程開銷
    • CPU的發展
    • 使用線程的理由
  • 如何寫一個簡單Parallel.For迴圈
    • 數據並行
    • Parallel.For剖析

  優秀軟體的一個關鍵特征就是具有併發性。過去的幾十年,我們可以進行併發編程,但是
難度很大。以前,併發性軟體的編寫、調試和維護都很難,這導致很多開發人員為圖省事
放棄了併發編程。新版 .NET 中的程式庫和語言特征,已經讓併發編程變得簡單多了。隨
著 Visual Studio 2012 的發佈,微軟明顯降低了併發編程的門檻。以前只有專家才能做併發
編程,而今天,每一個開發人員都能夠(而且應該)接受併發編程。

  許多個人電腦和工作站都有多核CPU,可以同時執行多個線程。為了充分利用硬體,您可以將代碼並行化,以便跨多個處理器分發工作。

  在過去,並行需要對線程和鎖進行低級操作。Visual Studio和.NET框架通過提供運行時、類庫類型和診斷工具來增強對並行編程的支持。這些特性是在.NET Framework 4中引入的,它們使得並行編程變得簡單。您可以用自然的習慣用法編寫高效、細粒度和可伸縮的並行代碼,而無需直接處理線程或線程池。

  下圖展示了.NET框架中並行編程體繫結構。

使用場景

1 基本概念

1.1 併發編程

  • 併發

    同時做多件事情

  這個解釋直接表明瞭併發的作用。終端用戶程式利用併發功能,在輸入資料庫的同時響應用戶輸入。伺服器應用利用併發,在處理第一個請求的同時響應第二個請求。只要你希望程式同時做多件事情,你就需要併發。

  • 多線程

    併發的一種形式,它採用多個線程來執行程式。從字面上看,多線程就是使用多個線程。多線程是併發的一種形式,但不是唯一的形式。

  • 並行處理

    把正在執行的大量的任務分割成小塊,分配給多個同時運行的線程。

  為了讓處理器的利用效率最大化,並行處理(或並行編程)採用多線程。當現代多核 CPU行大量任務時,若只用一個核執行所有任務,而其他核保持空閑,這顯然是不合理的。

  並行處理把任務分割成小塊並分配給多個線程,讓它們在不同的核上獨立運行。並行處理是多線程的一種,而多線程是併發的一種。

  • 非同步編程

    併發的一種形式,它採用 future 模式或回調(callback)機制,以避免產生不必要的線程。

  一個 future(或 promise)類型代表一些即將完成的操作。在 .NET 中,新版 future 類型
有 Task 和 Task 。在老式非同步編程 API 中,採用回調或事件(event),而不是
future。非同步編程的核心理念是非同步操作:啟動了的操作將會在一段時間後完成。這個操作
正在執行時,不會阻塞原來的線程。啟動了這個操作的線程,可以繼續執行其他任務。當
操作完成時,會通知它的future,或者調用回調函數,以便讓程式知道操作已經結束。

NOTE:通常情況下,一個併發程式要使用多種技術。大多數程式至少使用了多線程(通過線程池)和非同步編程。要大膽地把各種併發編程形式進行混合和匹配,在程式的各個部分使用
合適的工具。

1.2 TPL

  任務並行庫(TPL)是System.Threading和System.Threading.Tasks命名空間中的一組公共類型和API。

  TPL動態地擴展併發度,以最有效地使用所有可用的處理器。通過使用TPL,您可以最大限度地提高代碼的性能,同時專註於您的代碼的業務實現。

  從.NET Framework 4開始,TPL是編寫多線程和並行代碼的首選方式。

2 線程基礎

2.1 Windows 為什麼要支持線程

  在電腦的早期歲月,操作系統沒提供線程的概念。事實上,整個系統只運行著一個執行線程(單線程),其中同時包含操作系統代碼和應用程式代碼。只用一個執行線程的問題在於,長時間運行的任務會阻止其他任務執行。
例如,在16位Windows的那些日子里,列印一個文檔的應用程式很容易“凍結”整個機器,造成OS和其他應用程式停止響應。有的程式含有bug,會造成死迴圈。遇到這個問題,用戶只好重啟電腦。用戶對此深惡痛絕。

  於是微軟下定決心設計一個新的OS,這個OS必須健壯,可靠,易於是伸縮以安全,同同時必須改進16位windows的許多不足。

  微軟設計這個OS內核時,他們決定在一個進程(Process)中運行應用程式的每個實例。進程不過是應用程式的一個實例要使用的資源的一個集合。每個進程都被賦予一個虛擬地址空間,確保一個進程使用的代碼和數據無法由另一個進程訪問。這就確保了應用程式實例的健壯性。由於應用程式破壞不了其他應用程式或者OS本身,所以用戶的計算體驗變得更好了。

  聽起來似乎不錯,但CPU本身呢?如果一個應用程式進入無限迴圈,會發生什麼呢?如果機器中只有一個CPU,它會執行無限迴圈,不能執行其它任何東西。所以,雖然數據無法被破壞,而且更安全,但系統仍然可能停止響應。微軟要修複這個問題,他們拿出的方案就是線程。作為Windows概念,線程的職責是對CPU進行虛擬化。Windows為每個進程都提供了該進程專用的專用的線程(功能相當於一個CPU,可將線程理解成一個邏輯CPU)。如果應用程式的代碼進入無限迴圈,與那個代碼關聯的進程會被“凍結”,但其他進程(他們有自己的線程)不會凍結:他們會繼續執行!

2.2 線程開銷

  線程是一個非常強悍的概念,因為他們使windows即使在執行長時間運行的任務時也能隨時響應。另外,線程允許用戶使用一個應用程式(比如“任務管理器”)強制終止似乎凍結的一個應用程式(它也有可能正在執行一個長時間運行的任務)。但是,和一切虛擬化機制一樣,線程會產生空間(記憶體耗用)和時間(運行時的執行性能)上的開銷。

  創建線程,讓它進駐系統以及最後銷毀它都需要空間和時間。另外,還需要討論一下上下文切換。單CPU的電腦一次只能做一件事情。所以,windows必須在系統中的所有線程(邏輯CPU)之間共用物理CPU。

在任何給定的時刻,Windows只將一個線程分配給一個CPU。那個線程允許運行一個時間片。一旦時間片到期,Windows就上下文切換到另一個給線程。每次上下文切換都要求Windows執行以下操作:

  • 將CPU寄存器中的值保存到當前正在運行的線程的內核對象內部的一個上下文結構中。
  • 從現有線程集合中選一個線程供調度(切換到的目標線程)。如果該線程由另一個進程擁有,Window在開始執行任何代碼或者接觸任何數據之前,還必須切換CPU“看得見”的虛擬地址空間。
  • 將所選上下文結構中的值載入到CPU的寄存器中。

  上下文切換完成後,CPU執行所選的線程,直到它的時間片到期。然後,會發生新一輪的上下文切換。Windows大約每30ms執行一次上下文切換。

  上下文切換是凈開銷:也就是說上下文切換所產生的開銷不會換來任何記憶體或性能上的收益。

  根據上述討論,我們的結論是必須儘可能地避免使用線程,因為他們要耗用大量的記憶體,而且需要相當多的時間來創建,銷毀和管理。Windows線上程之間進行上下文切換,以及在發生垃圾回收的時候,也會浪費不少時間。然而,根據上述討論,我們還得出一個結論,那就是有時候必須使用線程,因為它們使Windows變得更健壯,反應更靈敏。

  應該指出的是,安裝了多個CPU或者一個多核CPU)的電腦可以真正同時運行幾個線程,這提升了應用程式的可伸縮性(在少量的時間里做更多工作的能力)。Windows為每個CPU內核都分配一個線程,每個內核都自己執行到其他線程的上下文切換。Windows確保單個線程不會在多個內核上同時被調度,因為這會代理巨大的混亂。今天,許多電腦都包含了多個CPu,超線程CPU或者多核CPU。但是,windows最初設計時,單CPU電腦才是主流,所以Windows設計了線程來增強系統的響應能力和可靠性。今天,線程還被用於增強應用程式的可伸縮性,但在只有多CPU(或多核CPU)電腦上才有可能發生。

TIP:一個時間片結束時,如果Windows決定再次調度同一個線程(而不是切換到另外給一個線程),那麼Windows不會執行上下文切換。線程將繼續執行,這顯著改進了性能。設計自己的代碼時註意,上下文切換能避免的就要儘量避免

2.3 CPU的發展

  過去,CPU速度一直隨著時間在變快。所以,在一臺舊機器上運行得慢的程式在新機器上一般會快些。然而,CPU 廠商沒有延續CPU越來越快的趨勢。由於CPU廠商不能做到一直提升CPU的速度,所以它們側重於將晶體管做得越來越小,使一個晶元上能夠容納更多的晶體管。今天,一個硅晶元可以容納2個或者更多的CPU內核。這樣一來,如果在寫軟體時能利用多個內核,軟體就能運行得更快些。

  今天的電腦使用了以下三種多CPU技術。

  • 多個CPU
  • 超線程晶元
  • 多核晶元

2.4 使用線程的理由

  使用線程有以下三方面的理由。

  • 使用線程可以將代碼同其他代碼隔離

  這將提高應用程式的可靠性。事實上,這正是Windows在操作系統中引入線程概念的原因。Windows之所以需要線程來獲得可靠性,是因為你的應用程式對於操作系統來說是的第三方組件,而微軟不會在你發佈應用程式之前對這些代碼進行驗證。如果你的應用程式支持載入由其它廠商生成的組件,那麼應用程式對健壯性的要求就會很高,使用線程將有助於滿足這個需求。

  • 可以使用線程來簡化編碼

  有的時候,如果通過一個任務自己的線程來執行該任務,或者說單獨一個線程來處里該任務,編碼會變得更簡單。但是,如果這樣做,肯定要使用額外的資源,也不是十分“經濟”(沒有使用儘量少的代碼達到目的)。現在,即使要付出一些資源作為代價,我也寧願選擇簡單的編碼過程。否則,乾脆堅持一直用機器語言寫程式好了,完全沒必要成為一名C#開發人員。但有的時候,一些人在使用線程時,覺得自己選擇了一種更容易的編碼方式,但實際上,它們是將事情(和它們的代碼)大大複雜化了。通常,在你引入線程時,引入的是要相互協作的代碼,它們可能要求線程同步構造知道另一個線程在什麼時候終止。一旦開始涉及協作,就要使用更多的資源,同時會使代碼變得更複雜。所以,在開始使用線程之前,務必確定線程真的能夠幫助你。

  • 可以使用線程來實現併發執行
      如果(而且只有)知道自己的應用程式要在多CPU機器上運行,那麼讓多個任務同時運行,就能提高性能。現在安裝了多個CPU(或者一個多核CPU)的機器相當普遍,所以設計應用程式來使用多個內核是有意義的。

3 數據並行(Data Parallelism)

3.1 數據並行

  數據並行是指對源集合或數組中的元素同時(即並行)執行相同操作的情況。在數據並行操作中,源集合被分區,以便多個線程可以同時在不同的段上操作。

  數據並行性是指對源集合或數組中的元素同時任務並行庫(TPL)通過system.threading.tasks.parallel類支持數據並行。這個類提供了for和for each迴圈的基於方法的並行實現。

  您為parallel.for或parallel.foreach迴圈編寫迴圈邏輯,就像編寫順序迴圈一樣。您不必創建線程或將工作項排隊。在基本迴圈中,您不必使用鎖。底層工作TPL已經幫你處理。

  下麵代碼展示順序和並行:

// Sequential version            
foreach (var item in sourceCollection)
{
    Process(item);
}

// Parallel equivalent
Parallel.ForEach(sourceCollection, item => Process(item));

  並行迴圈運行時,TPL對數據源進行分區,以便迴圈可以同時在多個部分上運行。在後臺,任務調度程式根據系統資源和工作負載對任務進行分區。如果工作負載變得不平衡,調度程式會在多個線程和處理器之間重新分配工作。

  下麵的代碼來展示如何通過Visual Studio調試代碼:

public static void test()
        {
            int[] nums = Enumerable.Range(0, 1000000).ToArray();
            long total = 0;
            
            // Use type parameter to make subtotal a long, not an int
            Parallel.For<long>(0, nums.Length, () => 0, (j, loop, subtotal) =>
            {
                subtotal += nums[j];
                return subtotal;
            },
                (x) => Interlocked.Add(ref total, x)
            );

            Console.WriteLine("The total is {0:N0}", total);
            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
        }
  • 選擇調試 > 開始調試,或按F5。
    應用在調試模式下啟動,並會在斷點處暫停。

  • 在中斷模式下打開線程通過選擇視窗調試 > Windows > 線程。 您必須位於一個調試會話以打開或請參閱線程和其他調試視窗。

使用場景

3.2 Parallel.For剖析

  查看Parallel.For的底層,

 public static ParallelLoopResult For<TLocal>(int fromInclusive, int toExclusive, Func<TLocal> localInit, Func<int, ParallelLoopState, TLocal, TLocal> body, Action<TLocal> localFinally);

  清楚的看到有個func函數,看起來很熟悉。

 [TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")]
    public delegate TResult Func<out TResult>();

原來是定義的委托,有多個重載,具體查看文檔[https://docs.microsoft.com/en-us/dotnet/api/system.func-4?view=netframework-4.7.2]

  實際上TPL之前,實現併發或多線程,基本都要使用委托。

TIP:關於委托,大家可以查看(https://docs.microsoft.com/en-us/dotnet/csharp/tour-of-csharp/delegates)。或者《細說委托》(https://www.cnblogs.com/laoyu/archive/2013/01/13/2859000.html)

參考

使用場景


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 如果你對多線程的控制不怎麼瞭解,那麼理解了這篇文章的內容也許對你有幫助。鼓勵先自己動手實現一遍,做不出來在看代碼。 題目一:兩個線程交替列印0~100的奇偶數 這道題就是說有兩個線程,一個名為偶數線程,一個名為奇數線程,偶數線程只列印偶數,奇數線程只列印奇數,兩個線程按順序交替列印。本文重點不是說的 ...
  • c#中如何使用到模糊查詢,先舉個最簡單實用的例子,可在vs控制台應用程式中輸出: 定義實體類: public class Student { public int ID { get; set; } public string Name { get; set; } public int? Age { ...
  • 一. Identity 介紹 ASP.NET Core Identity是一個會員系統,可為ASP.NET Core應用程式添加登錄功能。可以使用SQL Server資料庫配置身份以存儲用戶名,密碼和配置文件數據。或者,可以使用另一個持久性存儲,例如,Azure表存儲。下麵學習如何使用Identit ...
  • C# ToString()日期格式 ToString:2016/9/27 0:00:00ToString("yyyy/MM/dd"):2016/09/27ToString("yyyy-MM-dd"):2016-09-27ToString("yyyy.MM.dd"):2016.09.27ToStrin ...
  • 今天日了gou了,一大早打開VS2017的時候出現無法連接到Web伺服器“IIS Express”的錯誤,然後必應了一下,再谷歌了一下找到的解決方法也都千篇一律,奈何都沒能解決,最後通過靜下心來的思考,嘗試解決了問題,特此記錄一下,可能沒有第二個人會遇到跟我相同的問題了吧!先上問題截圖: 問題回顧 ...
  • 這篇文章介紹一下,如何使用VS2017給asp.net core添加容器支持,併發布鏡像到私有docker hub,然後用chart管理容器鏡像的操作流程。 話不多說,just do it. 新建項目 首先新建一個asp.net core項目,這裡我新建一個WebApi預設項目。 這裡我就不啟動項目 ...
  • 參考微軟官方文檔-特殊字元@,地址 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/tokens/verbatim 1、在變數名前加@,可以告訴編譯器,@後的就是變數名。主要用於變數名和C#關鍵字重覆時使用。 2、在 ...
  • 執行dotnet-new selfhost sstest 創建項目,然後打開解決方案 修改ssTest.ServiceModel中的Hello.cs,在HellopResponse中添加時間屬性,然後修改MyServices中的代碼 運行程式,打開Postman查看返回結果 可以看到Json中dat ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...