Golang的CSP很酷?其實.NET也能夠輕鬆完成

提及Golang(後面統稱爲Go),就想到他的高併發特性,在深刻一些就是 Goroutine。在你們被它優雅的語法和簡潔的代碼實現的高併發程序所折服時,其實C#/.NET也能夠很容易的作到。今天咱們來參照Go,來用C#實現它所採用的的CSP併發模型。html

CSP(Communicating sequential processes)

這東西我一開始覺得很簡單,後面差了資料發現它獨樹一幟,本身是一門語言,也是一套理論。這邊我不深刻的對它作過多的看法,我怕耽誤你們=_=,你們能夠看看wiki。算法

wiki:https://en.wikipedia.org/wiki/Communicating_sequential_processes編程

咱們從Go的角度對它進行一些分析,摘抄一段概要:多線程

「用於描述兩個獨立的併發實體經過共享的通信 channel(管道)進行通訊的併發模型。 CSP中channel是第一類對象,它不關注發送消息的實體,而關注與發送消息時使用的channel。」併發

好了,單獨寫出 CSP 是爲了讓你們瞭解這是一套獨立於語言的東西,你們有興趣能夠查看wiki和搜索一些其它資料。異步

在Go中的CSP

Channel(通道)

Goroutine(不知道怎麼翻譯,你們能夠理解成一個「工做者」,不是工做者線程。本質是實現了協程。)編程語言

協程(提高併發的利器)

你們都很明白線程能作什麼,但協程是個什麼東西?比起線程又如何呢?異步編程

線程

咱們從新思考一些東西。高併發

CPU:核心、超線程性能

OS:線程

編程語言:線程池

這邊不作細講,只是大概點到一下。

咱們所作的任何計算都要經由CPU計算,而CPU的核數直接決定了咱們能給CPU執行幾件事情。

咱們如今所經常使用的OS內部都有一個輪詢,用時間片的形式來分配任何輪流使用CPU執行計算,線程就是這些任務的載體。

這塊的概念很是龐大(還有牽扯到,什麼是併發,什麼事並行),本文的重點不是這些,你們有興趣後面能夠單獨開一篇文章來解釋這塊的內容。

迴歸本文,如今咱們知道線程是操做系統級別用來共享CPU的一種技術實現,多線程編程早在各大語言遍地開花,被用的唯妙唯肖,百花齊放。

那麼爲何須要協程呢?

線程的開銷

這塊又是一個大知識點,這邊也很少作介紹。

你們只要明白,線程並非廉價的,一個線程的創立有至少兩點的開銷

  1. 內存
  2. 調度器壓力(線程上下文切換等)

線程是能夠持有邏輯數據的(好比,HttpContext.Current,等對象)因此一定是佔用內存的(至於佔用了多少內存不一樣的語言和OS不同)

若是一個CPU是4核的,同時就只能處理4件任務,一個OS的線程越多他們輪訓一整圈所耗的時間就更長。而每次調度線程時都須要複製當前線程上下文的狀態,再去讀取準備調度線程上下文的狀態。

這邊能夠看到最後一點,有時候多線程反而會比單線程更加的慢,因此多線程提高性能本質上實際上是假的。多線程並不會提高程序性能。

我知道這邊確定有人會心存疑問,絕大數的人都說用多線程來提高性能,爲何這邊說多線程會比單線程慢?

咱們這邊想一下:PHP 和 NodeJS,PHP默認不支持多線程,NodeJS採用單線程事件輪詢,他們的效率比擁有多線程的語言低嗎?並不會。

多線程之因此快是由於做弊,別人一我的乾的事情你叫兩我的去幹固然會比單線程快。這也有很是大的限制,多線程所執行的東西儘量避免共享,否則你的效率仍是可能不如單線程。

這邊說的有點跑題,這塊的內容實在太大,你們只要知道,線程即便不昂貴也毫不廉價。

針對這個問題,各大語言都推出了一個叫作線程池的技術,我申請一批線程,持有他,等到有任務的時候直接使用,這樣我就不會頻繁的建立和銷燬線程了。這樣大大提高了效率。

在.NET中,很早就提倡任何須要線程的時刻都使用 ThreadPool。

ps:如今覺大多數(我還沒見過)的語言(runtime)中,線程與操做系統的線程是一一對應的。

迴歸協程

協程與線程是多對一的關係,有多個協程會對應到一根線程上。跟線程和CPU是同樣的關係。

線程是爲了共享CPU,而協程是爲了共享線程。

協程是應用層面的自有「線程」實現。也就是說在不改變OS的線程邏輯下,本身構建了一套 「線程」系統。

爲何不直接改動OS的線程,讓其更輕?我我的以爲 1是歷史兼容性問題,2是必要性問題,線程是一個很好的抽象邏輯。實現協程徹底能夠經過線程來完成。

協程的目的

咱們來思考一個場景

抓取百度、google、bing的html。

多線程的作法是

啓動三個線程,分別對百度、google、bing發起HTTP GET請求。這時候使用了三個線程。

協程的作法是(極端)

啓動一個線程對百度發起HTTP GET請求,將任務放入隊列,在對google發起HTTP GET請求,將任務放入隊列,在對bingHTTP GET請求將任務放入隊列。

這時候只須要使用一個線程(極端狀況下,其實大多數實現來講至少須要兩個線程,由於須要有一個後臺線程去監放任務隊列,當任務完成後再分配一個可用線程去處理下面的邏輯)

爲何說極端狀況下?由於協程有時候也可能會與線程一一對應,好比你的CPU有8個核心,同時跑4個協程也有可能會分配4根線程單獨去處理這4個任務,這主要取決於調度算法。

總結:協程是爲了提高線程利用率,減小線程的無用功(大多數是IO堵塞),協程也更適合IO密集型的場景。

C#中的協程

image

能夠看到,3個任務是異步執行的,但都由線程4來處理,也就是說三個異步任務只用了一根線程。

C#中的CSP

講了這麼大篇幅的協程,終於迴歸了今天的主題。

其實單單實現CSP來講根本不用理清線程和協程。但今天主要對比的是Go中的CSP,因此若是沒有協程基本是沒有意義的。

C#如何對應,CSP中最重要的Channel呢?

答案就是:BlockingCollection<T>

咱們來看一個例子

抓取一批網站並輸出網站的title

發起 HTTP GET 請求 和分析Title的代碼邏輯以下:

image

主程序的代碼以下:

image

執行邏輯

  1. 啓用一個生產者協程來根據url生產對應的html、同時使用主線程消費隊列內的內容(異步)
  2. 每一個url單獨起一個協程來發起HTTP GET請求
  3. 生產者協程等待全部url的html所有加載完成
  4. 標誌隊列完成
  5. 主線程退出

執行結果以下:

image

Go協程與.NET協程的區別?

去除實現上的一些邏輯,本質上沒太多區別。

但Go有一個天生優點就是它是新時代的語言,拋棄了線程。也就是說Go層面沒有線程的東西,它只有協程。

但.NET中線程已經擁有了好多年,大量的類庫、驅動使用線程來完成。

因此你在上一層就算使用了協程,執行到底部不必定只有一根線程來完成,底部能夠本身建立線程來運行邏輯,今天篇幅關係不作過多說明。後面咱們在介紹這塊的內容。

寫在最後

最後總結一個要點,多線程、協程並不能提高性能,它們所達到的目的只是提升CPU利用率。

今天原本想詳細寫BlockingCollection<T>的使用說明,但協程等概念佔了大量的篇幅,後面咱們再來詳細介紹.NET中的異步編程。

.NET技術棧QQ羣:384413261(點擊加入 .NET Group

相關文章
相關標籤/搜索