提及Golang(後面統稱爲Go),就想到他的高併發特性,在深刻一些就是 Goroutine。在你們被它優雅的語法和簡潔的代碼實現的高併發程序所折服時,其實C#/.NET也能夠很容易的作到。今天咱們來參照Go,來用C#實現它所採用的的CSP併發模型。html
這東西我一開始覺得很簡單,後面差了資料發現它獨樹一幟,本身是一門語言,也是一套理論。這邊我不深刻的對它作過多的看法,我怕耽誤你們=_=,你們能夠看看wiki。算法
wiki:https://en.wikipedia.org/wiki/Communicating_sequential_processes編程
咱們從Go的角度對它進行一些分析,摘抄一段概要:多線程
「用於描述兩個獨立的併發實體經過共享的通信 channel(管道)進行通訊的併發模型。 CSP中channel是第一類對象,它不關注發送消息的實體,而關注與發送消息時使用的channel。」併發
好了,單獨寫出 CSP 是爲了讓你們瞭解這是一套獨立於語言的東西,你們有興趣能夠查看wiki和搜索一些其它資料。異步
Goroutine(不知道怎麼翻譯,你們能夠理解成一個「工做者」,不是工做者線程。本質是實現了協程。)編程語言
你們都很明白線程能作什麼,但協程是個什麼東西?比起線程又如何呢?異步編程
咱們從新思考一些東西。高併發
CPU:核心、超線程性能
OS:線程
編程語言:線程池
這邊不作細講,只是大概點到一下。
咱們所作的任何計算都要經由CPU計算,而CPU的核數直接決定了咱們能給CPU執行幾件事情。
咱們如今所經常使用的OS內部都有一個輪詢,用時間片的形式來分配任何輪流使用CPU執行計算,線程就是這些任務的載體。
這塊的概念很是龐大(還有牽扯到,什麼是併發,什麼事並行),本文的重點不是這些,你們有興趣後面能夠單獨開一篇文章來解釋這塊的內容。
迴歸本文,如今咱們知道線程是操做系統級別用來共享CPU的一種技術實現,多線程編程早在各大語言遍地開花,被用的唯妙唯肖,百花齊放。
那麼爲何須要協程呢?
這塊又是一個大知識點,這邊也很少作介紹。
你們只要明白,線程並非廉價的,一個線程的創立有至少兩點的開銷
線程是能夠持有邏輯數據的(好比,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密集型的場景。
能夠看到,3個任務是異步執行的,但都由線程4來處理,也就是說三個異步任務只用了一根線程。
講了這麼大篇幅的協程,終於迴歸了今天的主題。
其實單單實現CSP來講根本不用理清線程和協程。但今天主要對比的是Go中的CSP,因此若是沒有協程基本是沒有意義的。
C#如何對應,CSP中最重要的Channel呢?
答案就是:BlockingCollection<T>
咱們來看一個例子
抓取一批網站並輸出網站的title
發起 HTTP GET 請求 和分析Title的代碼邏輯以下:
主程序的代碼以下:
執行邏輯
執行結果以下:
去除實現上的一些邏輯,本質上沒太多區別。
但Go有一個天生優點就是它是新時代的語言,拋棄了線程。也就是說Go層面沒有線程的東西,它只有協程。
但.NET中線程已經擁有了好多年,大量的類庫、驅動使用線程來完成。
因此你在上一層就算使用了協程,執行到底部不必定只有一根線程來完成,底部能夠本身建立線程來運行邏輯,今天篇幅關係不作過多說明。後面咱們在介紹這塊的內容。
最後總結一個要點,多線程、協程並不能提高性能,它們所達到的目的只是提升CPU利用率。
今天原本想詳細寫BlockingCollection<T>的使用說明,但協程等概念佔了大量的篇幅,後面咱們再來詳細介紹.NET中的異步編程。
.NET技術棧QQ羣:384413261(點擊加入 .NET Group)