C#併發編程-1 併發編程概述

来源:https://www.cnblogs.com/wwwen/archive/2022/07/28/16528465.html
-Advertisement-
Play Games

一 併發編程簡介 1.1 關於併發和並行 併發和並行的概念: 併發:(Concurrent),在某個時間段內,如果有多個任務執行,即有多個線程在操作時,如果系統只有一個CPU,則不能真正同時進行一個以上的線程, 它只能把CPU運行時間劃分成若幹個時間段,再將時間段分配給各個線程執行,在一個時間段的線 ...


一 併發編程簡介

1.1 關於併發和並行

併發和並行的概念:

  • 併發:(Concurrent),在某個時間段內,如果有多個任務執行,即有多個線程在操作時,如果系統只有一個CPU,則不能真正同時進行一個以上的線程,

                它只能把CPU運行時間劃分成若幹個時間段,再將時間段分配給各個線程執行,在一個時間段的線程代碼運行時,其它線程處於掛起狀態。

  • 並行:(Parallel),當系統有一個以上CPU時,則線程的操作有可能非併發。

                當一個CPU執行一個線程時,另一個CPU可以執行另一個線程,兩個線程互不搶占CPU資源,可以同時進行。

併發和並行是即相似又有區別的兩個概念:

  • 併發是指兩個或多個事件在同一時間間隔內發生;
  • 並行是指兩個或者多個事件在同一時刻發生。

1.2 關於併發編程和並行編程

併發編程的形式:

  • 多線程編程:使用多個線程執行程式;
  • 響應式編程。
  • 非同步編程:APM,EAP,TAP。推薦使用 async await關鍵字的TAP ( Task-based Asynchronous Pattern );

並行編程:把正在執行的大量任務分割成小塊,分配個多個同時運行的線程,讓它們在不同的核上獨立運行。讓處理器的利用效率最大化。

 

二 非同步編程簡介

2.1 async await關鍵字

現代的非同步.Net程式使用兩個關鍵字,async和await。async關鍵字加在方法聲明上,它的主要目的是使方法內的await關鍵字生效。

如果async方法有返回值,應返回Task<T>,如果沒有返回值,應返回Task。Task類型相當於future,用來在非同步方法結束時通知調用程式。

一個非同步方法例子:

async Task MethodAsync() 
{
    var i = 10;
    await Task.Delay(1000);
    i++;
    await Task.Delay(1000);
    Trace.WriteLine(i);
}

async方法在開始時以同步方式執行。在async方法內部,await關鍵字對它的參數執行一個非同步等待。

它首先檢測操作是否已經完成,如果完成了,就繼續以同步方式運行,否則會暫停方法,並返回,留下一個未完成的Task。

一段時間後,操作完成,async方法就恢復,從await出繼續往下執行。

 2.2 ConfigureAwait ( )

一個 async 方法是由多個同步執行的程式塊組成的,每個同步程式塊之間由 await 語句分隔。

第一個同步程式塊在調用這個方法的線程中運行,那await之後的同步程式塊在哪個線程運行呢?

一個常見的情況是,用 await 語句等待一個任務完成,當該方法在 await 處暫停時,就可以捕捉上下文(context)。

如果當前 SynchronizationContext 不為空,這個上下文就是當前SynchronizationContext。如果當前 SynchronizationContext 為空,則這個上下文為當前TaskScheduler。

該方法會在這個上下文中繼續運行。一般來說,運行 UI 線程時採用 UI 上下文,處理 ASP.NET 請求時採用 ASP.NET 請求上下文,其他很多情況下則採用線程池上下文。

因此,在上面的代碼中,每個同步程式塊會試圖在原始的上下文中恢復運行。如果在 UI線程中調用 MethodAsync,這個方法的每個同步程式塊都將在此 UI 線程上運行。但

是,如果線上程池線程中調用,每個同步程式塊將線上程池線程上運行。

要控制這種行為,可以在 await 中使用 ConfigureAwait 方法,將參數 continueOnCapturedContext設為 false。接下來的代碼剛開始會在調用的線程里運行,在被 await 暫停後,

則會線上程池線程里繼續運行。

async Task MethodAsync() 
{
    //在調用線程運行
    var i = 10;
    await Task.Delay(1000).ConfigureAwait(false);
    //線上程池線程運行
    i++;
    await Task.Delay(1000);
    Trace.WriteLine(i);
}

 2.3 異常處理

使用async await時,自然要處理異常。一旦非同步方法拋出異常,該異常會放在返回的 Task 對象中,並且這個 Task對象的狀態變為“已完成”。

當 await 調用該 Task 對象時,await 會獲得並(重新)拋出該異常,並且保留著原始的棧軌跡。

async Task MethodAsync() 
{
    //發生異常時,任務結束,不會直接拋出異常。
    var task = ThrowExceptionAsync();
    try
    {
        //await時,拋出異常
        await task;
    }
    catch (Exception ex)
    {
        Trace.WriteLine(ex.StackTrace);
    }
}

async Task ThrowExceptionAsync() 
{
    await Task.Delay(1000);
    throw new Exception("ThrowExceptionAsync");
}

2.4 一旦在代碼中使用了非同步,最好一直使用

關於非同步方法,還有一條重要的準則:一旦在代碼中使用了非同步,最好一直使用。調用非同步方法時,應該(在調用結束時)用 await 等待它返回的 task 對象。

一定要避免使用Task.Wait 或 Task<T>.Result 方法,因為它們會導致死鎖。參考一下下麵這個方法:

void Deadlock()
{
    // 開始延遲
    Task task = WaitAsync();
    // 同步程式塊,正在等待非同步方法完成
    task.Wait();
}
async Task WaitAsync()
{
    // 這裡 awati 會捕獲當前上下文
    await Task.Delay(TimeSpan.FromSeconds(1));
    // 這裡會試圖用上面捕獲的上下文繼續執行
}

如果從 UI 或 ASP.NET 的上下文調用這段代碼,就會發生死鎖。這是因為,這兩種上下文每次只能運行一個線程。Deadlock 方法調用 WaitAsync 方法,WaitAsync 方法開始調用delay 語句。

然後,Deadlock 方法(同步)等待 WaitAsync 方法完成,同時阻塞了上下文線程。當 delay 語句結束時,await 試圖在已捕獲的上下文中繼續運行 WaitAsync 方法,但這個步驟無法成功,

因為上下文中已經有了一個阻塞的線程,並且這種上下文只允許同時運行一個線程。

這裡有兩個方法可以避免死鎖:

在 WaitAsync 中使用 ConfigureAwait(false)(導致 await 忽略該方法的上下文),

或者用 await 語句調用 WaitAsync 方法(讓 Deadlock變成一個非同步方法)。

 

三 並行編程簡介

如果程式中有大量的計算任務,並且這些任務能分割成幾個互相獨立的任務塊,可以考慮使用並行編程。並行編程可臨時提高 CPU 利用率,以提高吞吐量。

並行的形式有兩種:

  • 數據並行(data parallelism):有大量的數據需要處理,並且每一塊數據的處理過程基本上是彼此獨立的。

  • 任務並行(task parallelim):需要執行大量任務,並且每個任務的執行過程基本上是彼此獨立的。任務並行可以是動態的,如果一個任務的執行結果會產生額外的任務,這些新增的任務也可以加入任務池。

3.1 數據並行

實現數據並行有幾種不同的做法。

一種做法是使用 Parallel.ForEach 方法,它類似於foreach 迴圈,應儘可能使用這種做法。

Parallel 類也提供 Parallel.For 方法,這類似於 for 迴圈,當數據處理過程基於一個索引時,可使用這個方法。

另一種做法是使用 PLINQ(Parallel LINQ), 它為 LINQ 查詢提供了 AsParallel 擴展。

與PLINQ 相比,Parallel 對資源更加友好,Parallel 與系統中的其他進程配合得比較好 , 而PLINQ 會試圖讓所有的 CPU 來執行本進程。

//Parallel.ForEach
void RotateMatrices(IEnumerable<Matrix> matrices, float degrees)
{
    Parallel.ForEach(matrices, matrix => matrix.Rotate(degrees));
}

//PLINQ
IEnumerable<bool> PrimalityTest(IEnumerable<int> values)
{
    return values.AsParallel().Select(val => IsPrime(val));
}

不管選用哪種方法,在並行處理時有一個非常重要的準則。只要任務塊是互相獨立的,並行性就能做到最大化。

一旦在多個線程中共用狀態,就必須以同步方式訪問這些狀態,那樣程式的並行性就變差了。

有多種方式可以控制並行處理的輸出。可以把結果存在某些併發集合,或者對結果進行聚合。

3.2 任務並行

任務並行關註執行任務,Parallel 類的 Parallel.Invoke 方法可以以 分叉 / 聯合”(fork/join)的方式的使任務並行。

調用該方法時,把要並行執行的委托(delegate)作為傳入參數:

void ProcessArray(double[] array)
{
    Parallel.Invoke(
    () => ProcessPartialArray(array, 0, array.Length / 2),
    () => ProcessPartialArray(array, array.Length / 2, array.Length)
    );
}
void ProcessPartialArray(double[] array, int begin, int end)
{
    // CPU 密集型的操作……
}

任務並行也強調任務塊的獨立性。委托(delegate)的獨立性越強,程式的執行效率就越高。

 

 四 併發編程的集合

併發編程所用到的集合有兩類:

  • 併發集合;
  • 不可變集合。

4.1 併發集合

多個線程可以用安全的方式同時更新併發集合。大多數併發集合使用快照snapshot),

當一個線程在增加或刪除數據時,另一個線程也能枚舉數據。比起給常規集合加鎖以保護數據的方式,採用併發集合的方式要高效得多。

4.2 不可變集合

不可變集合實際上是無法修改的。要修改一個不可變集合,需要建立一個新的集合來代表這個被修改了的集合。


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

-Advertisement-
Play Games
更多相關文章
  • 前言 😋 嗨嘍,大家好呀~這裡是愛看美女的茜茜吶 本次採集網介紹:圖書頻道-全球最大中文網上書店 專業提供小說傳記,青春文學,成功勵志,投資理財等各品類圖書 暢銷榜最新報價、促銷、評論信息,引領最新網上購書體驗! 環境使用 🎈: Python 3.8 Pycharm 模塊使用 🎠: reque ...
  • 1. 簡介 1.1什麼是Mybatis MyBatis 是一款優秀的持久層框架 它支持自定義 SQL、存儲過程以及高級映射。 MyBatis 免除了幾乎所有的 JDBC 代碼以及設置參數和獲取結果集的工作。 MyBatis 可以通過簡單的 XML 或註解來配置和映射原始類型、介面和 Java POJ ...
  • 問題描述 用python 讀取csv文件時,報錯utf-8' codec can't decode byte 0xff in position 0: invalid start byte 問題原因 打開所用的編碼方式不對,需要指定該csv文件所用編碼 解決方法 1.找到該csv文件所用編碼方法 用記 ...
  • 兄弟們,溫故而知新,可以為師矣。 就是說,我們所學過的東西,要去多複習,這樣才能總結出屬於自己的理解,這樣就可以做老師了。 但是我以為的我以為,後面可以改成,將自己所學及所領會的教給別人,這樣才能更加記憶深刻。 今日內容:Python將多個文件多列進行關聯 知識點 文件讀寫 基礎語法 異常處理 迴圈 ...
  • 背景 過去,我們運維著“能做一切”的大型單體應用程式。這是一種將產品推向市場的很好的方式,因為剛開始我們也只需要讓我們的第一個應用上線。 而且我們總是可以回頭再來改進它的。部署一個大應用總是比構建和部署多個小塊要容易。 集中式: 集群: 分散式: 分散式和集中式會配合使用。 我們在搭建網站的時候,為 ...
  • static關鍵字 1.Java中的靜態 1.1static修飾成員變數 static修飾的成員變數屬於類、也稱為類變數,類對象可以使用。使用時可以直接用類名調用。 定義格式:`static 數據類型 變數名;` 例子: class A{ static String city="China"; } ...
  • 1.此為GitHub項目的學習記錄,記錄著我的思考,代碼基本都有註釋。 2.可以作為Python初學者鞏固基礎的絕佳練習,原題有些不妥的地方我也做了一些修正。 3.建議大家進行Python編程時使用英語,工作時基本用英語。 4.6~17題為level1難度,18-22題為level3難度,其餘都為l ...
  • [數據結構-線性表1.1] 數組(.NET源碼學習) 數組,一種數據類型(在絕大數語言中不是基本數據類型)且為引用類型,在記憶體中以連續的記憶體單元進行分配,所以其大小在創建對象後為定值,不可更改。 一.記憶體分配 對於兩種不同數據類型而言,其記憶體分配方式是不同的。值類型直接在棧(C#中稱為堆棧Stack ...
一周排行
    -Advertisement-
    Play Games
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...