C#代替go采用的CSP并發(fā)模型實(shí)現(xiàn)
說(shuō)起Golang(后面統(tǒng)稱為Go),就想到他的高并發(fā)特性,在深入一些就是 Goroutine。在大家被它優(yōu)雅的語(yǔ)法和簡(jiǎn)潔的代碼實(shí)現(xiàn)的高并發(fā)程序所折服時(shí),其實(shí)C#/.NET也可以很容易的做到。今天我們來(lái)參照Go,來(lái)用C#實(shí)現(xiàn)它所采用的的CSP并發(fā)模型。
CSP(Communicating sequential processes)
這東西我一開(kāi)始以為很簡(jiǎn)單,后面差了資料發(fā)現(xiàn)它獨(dú)樹(shù)一幟,自己是一門(mén)語(yǔ)言,也是一套理論。這邊我不深入的對(duì)它做過(guò)多的見(jiàn)解,我怕耽誤大家=_=,大家可以看看wiki。
wiki:https://en.wikipedia.org/wiki/Communicating_sequential_processes
我們從Go的角度對(duì)它進(jìn)行一些分析,摘抄一段概要:
“用于描述兩個(gè)獨(dú)立的并發(fā)實(shí)體通過(guò)共享的通訊 channel(管道)進(jìn)行通信的并發(fā)模型。 CSP中channel是第一類對(duì)象,它不關(guān)注發(fā)送消息的實(shí)體,而關(guān)注與發(fā)送消息時(shí)使用的channel。”
好了,單獨(dú)寫(xiě)出 CSP 是為了讓大家了解這是一套獨(dú)立于語(yǔ)言的東西,大家有興趣可以查看wiki和搜索一些其它資料。
在Go中的CSP
Channel(通道)
Goroutine(不知道怎么翻譯,大家可以理解成一個(gè)“工作者”,不是工作者線程。本質(zhì)是實(shí)現(xiàn)了協(xié)程。)
協(xié)程(提升并發(fā)的利器)
大家都很明白線程能做什么,但協(xié)程是個(gè)什么東西?比起線程又如何呢?
線程
我們重新思考一些東西。
CPU:核心、超線程
OS:線程
編程語(yǔ)言:線程池
這邊不做細(xì)講,只是大概點(diǎn)到一下。
我們所做的任何計(jì)算都要經(jīng)由CPU計(jì)算,而CPU的核數(shù)直接決定了我們能給CPU執(zhí)行幾件事情。
我們現(xiàn)在所常用的OS內(nèi)部都有一個(gè)輪詢,用時(shí)間片的形式來(lái)分配任何輪流使用CPU執(zhí)行計(jì)算,線程就是這些任務(wù)的載體。
這塊的概念非常龐大(還有牽扯到,什么是并發(fā),什么事并行),本文的重點(diǎn)不是這些,大家有興趣后面可以單獨(dú)開(kāi)一篇文章來(lái)解釋這塊的內(nèi)容。
回歸本文,現(xiàn)在我們知道線程是操作系統(tǒng)級(jí)別用來(lái)共享CPU的一種技術(shù)實(shí)現(xiàn),多線程編程早在各大語(yǔ)言遍地開(kāi)花,被用的惟妙惟肖,百花齊放。
那么為什么需要協(xié)程呢?
線程的開(kāi)銷
這塊又是一個(gè)大知識(shí)點(diǎn),這邊也不多做介紹。
大家只要明白,線程并不是廉價(jià)的,一個(gè)線程的創(chuàng)立有至少兩點(diǎn)的開(kāi)銷
- 內(nèi)存
- 調(diào)度器壓力(線程上下文切換等)
線程是可以持有邏輯數(shù)據(jù)的(比如,HttpContext.Current,等對(duì)象)所以必定是占用內(nèi)存的(至于占用了多少內(nèi)存不同的語(yǔ)言和OS不一樣)
如果一個(gè)CPU是4核的,同時(shí)就只能處理4件任務(wù),一個(gè)OS的線程越多他們輪訓(xùn)一整圈所耗的時(shí)間就更長(zhǎng)。而每次調(diào)度線程時(shí)都需要復(fù)制當(dāng)前線程上下文的狀態(tài),再去讀取準(zhǔn)備調(diào)度線程上下文的狀態(tài)。
這邊可以看到最后一點(diǎn),有時(shí)候多線程反而會(huì)比單線程更加的慢,所以多線程提升性能本質(zhì)上其實(shí)是假的。多線程并不會(huì)提升程序性能。
我知道這邊肯定有人會(huì)心存疑問(wèn),絕大數(shù)的人都說(shuō)用多線程來(lái)提升性能,為什么這邊說(shuō)多線程會(huì)比單線程慢?
我們這邊想一下:PHP 和 NodeJS,PHP默認(rèn)不支持多線程,NodeJS采用單線程事件輪詢,他們的效率比擁有多線程的語(yǔ)言低嗎?并不會(huì)。
多線程之所以快是因?yàn)樽鞅?,別人一個(gè)人干的事情你叫兩個(gè)人去干當(dāng)然會(huì)比單線程快。這也有非常大的限制,多線程所執(zhí)行的東西盡可能避免共享,不然你的效率還是可能不如單線程。
這邊說(shuō)的有點(diǎn)跑題,這塊的內(nèi)容實(shí)在太大,大家只要知道,線程即使不昂貴也絕不廉價(jià)。
針對(duì)這個(gè)問(wèn)題,各大語(yǔ)言都推出了一個(gè)叫做線程池的技術(shù),我申請(qǐng)一批線程,持有他,等到有任務(wù)的時(shí)候直接使用,這樣我就不會(huì)頻繁的創(chuàng)建和銷毀線程了。這樣大大提升了效率。
在.NET中,很早就提倡任何需要線程的時(shí)刻都使用 ThreadPool。
ps:現(xiàn)在覺(jué)大多數(shù)(我還沒(méi)見(jiàn)過(guò))的語(yǔ)言(runtime)中,線程與操作系統(tǒng)的線程是一一對(duì)應(yīng)的。
回歸協(xié)程
協(xié)程與線程是多對(duì)一的關(guān)系,有多個(gè)協(xié)程會(huì)對(duì)應(yīng)到一根線程上。跟線程和CPU是一樣的關(guān)系。
線程是為了共享CPU,而協(xié)程是為了共享線程。
協(xié)程是應(yīng)用層面的自有“線程”實(shí)現(xiàn)。也就是說(shuō)在不改變OS的線程邏輯下,自己構(gòu)建了一套 “線程”系統(tǒng)。
為什么不直接改動(dòng)OS的線程,讓其更輕?我個(gè)人覺(jué)得 1是歷史兼容性問(wèn)題,2是必要性問(wèn)題,線程是一個(gè)很好的抽象邏輯。實(shí)現(xiàn)協(xié)程完全可以通過(guò)線程來(lái)完成。
協(xié)程的目的
我們來(lái)思考一個(gè)場(chǎng)景
抓取百度、google、bing的html。
多線程的做法是
啟動(dòng)三個(gè)線程,分別對(duì)百度、google、bing發(fā)起HTTP GET請(qǐng)求。這時(shí)候使用了三個(gè)線程。
協(xié)程的做法是(極端)
啟動(dòng)一個(gè)線程對(duì)百度發(fā)起HTTP GET請(qǐng)求,將任務(wù)放入隊(duì)列,在對(duì)google發(fā)起HTTP GET請(qǐng)求,將任務(wù)放入隊(duì)列,在對(duì)bingHTTP GET請(qǐng)求將任務(wù)放入隊(duì)列。
這時(shí)候只需要使用一個(gè)線程(極端情況下,其實(shí)大多數(shù)實(shí)現(xiàn)來(lái)說(shuō)至少需要兩個(gè)線程,因?yàn)樾枰幸粋€(gè)后臺(tái)線程去監(jiān)聽(tīng)任務(wù)隊(duì)列,當(dāng)任務(wù)完成后再分配一個(gè)可用線程去處理下面的邏輯)
為什么說(shuō)極端情況下?因?yàn)閰f(xié)程有時(shí)候也可能會(huì)與線程一一對(duì)應(yīng),比如你的CPU有8個(gè)核心,同時(shí)跑4個(gè)協(xié)程也有可能會(huì)分配4根線程單獨(dú)去處理這4個(gè)任務(wù),這主要取決于調(diào)度算法。
總結(jié):協(xié)程是為了提升線程利用率,減少線程的無(wú)用功(大多數(shù)是IO堵塞),協(xié)程也更適合IO密集型的場(chǎng)景。
C#中的協(xié)程

可以看到,3個(gè)任務(wù)是異步執(zhí)行的,但都由線程4來(lái)處理,也就是說(shuō)三個(gè)異步任務(wù)只用了一根線程。
C#中的CSP
講了這么大篇幅的協(xié)程,終于回歸了今天的主題。
其實(shí)單單實(shí)現(xiàn)CSP來(lái)說(shuō)根本不用理清線程和協(xié)程。但今天主要對(duì)比的是Go中的CSP,所以如果沒(méi)有協(xié)程基本是沒(méi)有意義的。
C#如何對(duì)應(yīng),CSP中最重要的Channel呢?
答案就是:BlockingCollection<T>
我們來(lái)看一個(gè)例子
抓取一批網(wǎng)站并輸出網(wǎng)站的title
發(fā)起 HTTP GET 請(qǐng)求 和分析Title的代碼邏輯如下:

主程序的代碼如下:

執(zhí)行邏輯
- 啟用一個(gè)生產(chǎn)者協(xié)程來(lái)根據(jù)url生產(chǎn)對(duì)應(yīng)的html、同時(shí)使用主線程消費(fèi)隊(duì)列內(nèi)的內(nèi)容(異步)
- 每個(gè)url單獨(dú)起一個(gè)協(xié)程來(lái)發(fā)起HTTP GET請(qǐng)求
- 生產(chǎn)者協(xié)程等待所有url的html全部加載完成
- 標(biāo)志隊(duì)列完成
- 主線程退出
執(zhí)行結(jié)果如下:

Go協(xié)程與.NET協(xié)程的區(qū)別?
去除實(shí)現(xiàn)上的一些邏輯,本質(zhì)上沒(méi)太多區(qū)別。
但Go有一個(gè)天生優(yōu)勢(shì)就是它是新時(shí)代的語(yǔ)言,拋棄了線程。也就是說(shuō)Go層面沒(méi)有線程的東西,它只有協(xié)程。
但.NET中線程已經(jīng)擁有了好多年,大量的類庫(kù)、驅(qū)動(dòng)使用線程來(lái)完成。
所以你在上一層就算使用了協(xié)程,執(zhí)行到底部不一定只有一根線程來(lái)完成,底部可以自己創(chuàng)建線程來(lái)運(yùn)行邏輯,今天篇幅關(guān)系不做過(guò)多說(shuō)明。后面我們?cè)诮榻B這塊的內(nèi)容。
寫(xiě)在最后
最后總結(jié)一個(gè)要點(diǎn),多線程、協(xié)程并不能提升性能,它們所達(dá)到的目的只是提高CPU利用率。
今天本來(lái)想詳細(xì)寫(xiě)B(tài)lockingCollection<T>的使用說(shuō)明,但協(xié)程等概念占了大量的篇幅,后面我們?cè)賮?lái)詳細(xì)介紹.NET中的異步編程。
以上就是C#代替go采用的CSP并發(fā)模型實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于C#實(shí)現(xiàn)go采用CSP并發(fā)模型的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
WPF實(shí)現(xiàn)Badge標(biāo)識(shí)的示例代碼
這篇文章主要為大家詳細(xì)介紹了WPF如何實(shí)現(xiàn)Badge標(biāo)識(shí),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)或工作有一定幫助,感興趣的小伙伴可以了解一下2023-06-06
C#函數(shù)式編程中的遞歸調(diào)用之尾遞歸詳解
這篇文章主要介紹了C#函數(shù)式編程中的遞歸調(diào)用詳解,本文講解了什么是尾遞歸、尾遞歸的多種方式、尾遞歸的代碼實(shí)例等內(nèi)容,需要的朋友可以參考下2015-01-01
簡(jiǎn)單對(duì)比C#程序中的單線程與多線程設(shè)計(jì)
這篇文章主要介紹了C#程序中的單線程與多線程設(shè)計(jì)的簡(jiǎn)單對(duì)比,通過(guò)實(shí)際的代碼演示可以清晰看出多線程并發(fā)來(lái)避免單線程阻塞問(wèn)題的特點(diǎn),需要的朋友可以參考下2016-04-04
C# Bitmap圖像處理(含增強(qiáng)對(duì)比度的三種方法)
本文主要介紹了C# Bitmap圖像處理(含增強(qiáng)對(duì)比度的三種方法),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11

