溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Go語(yǔ)言中如何分析多進(jìn)程、多線程與協(xié)程的引入

發(fā)布時(shí)間:2022-01-17 16:10:03 來(lái)源:億速云 閱讀:292 作者:kk 欄目:大數(shù)據(jù)


Go語(yǔ)言中如何分析多進(jìn)程、多線程與協(xié)程的引入,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。

為什么要并發(fā)編程

在原生 PHP 中并沒(méi)有并發(fā)的概念,所有的操作都是串行執(zhí)行的、同步阻塞的,這也是很多人詬病 PHP 性能的原因,但是不支持并發(fā)編程的好處也是顯而易見(jiàn)的:保證了 PHP 的簡(jiǎn)單性,開(kāi)發(fā)者不必考慮并發(fā)引入的線程安全,也不需要在編程時(shí)權(quán)衡是否需要通過(guò)加鎖來(lái)保證某個(gè)操作的原子性,也沒(méi)有線程間通信問(wèn)題,魚(yú)和熊掌不可得兼,你不可能既要上手簡(jiǎn)單又要高性能,實(shí)際上,90%以上公司的業(yè)務(wù)和場(chǎng)景根本對(duì)性能沒(méi)有那么高的要求,傳統(tǒng)的 Nginx + PHP-FPM 完全以勝任了,如果非要在 PHP 中實(shí)現(xiàn)異步和并發(fā)編程,推薦使用 Swoole 擴(kuò)展來(lái)解決(實(shí)際上,Swoole 實(shí)現(xiàn)并發(fā)編程的協(xié)程功能正是借鑒了 Go 語(yǔ)言的協(xié)程實(shí)現(xiàn)機(jī)制)。

下面,我們書(shū)歸正傳,來(lái)介紹 Go 語(yǔ)言中并發(fā)編程的概念和實(shí)現(xiàn)。

與并發(fā)相對(duì)的是串行,即代碼按照順序一行一行執(zhí)行,當(dāng)遇到某個(gè)耗時(shí)的 IO 操作時(shí),比如發(fā)送郵件、查詢數(shù)據(jù)庫(kù)等,要等到該 IO 操作完成后才能繼續(xù)執(zhí)行下一行代碼,這在一些要求高并發(fā)高性能的業(yè)務(wù)場(chǎng)景中,顯然是不合適的,從整個(gè)操作系統(tǒng)層面來(lái)說(shuō),多個(gè)任務(wù)是可以并發(fā)執(zhí)行的,因?yàn)?CPU 本身通常是多核的,而且即使是單核 CPU,也可以通過(guò)時(shí)間分片的方式在多個(gè)進(jìn)程/線程之間切換執(zhí)行,從用戶角度來(lái)說(shuō),就好像它們?cè)凇竿瑫r(shí)發(fā)生」一樣,比如說(shuō),當(dāng)程序執(zhí)行到 IO 操作時(shí),我們可以掛起這個(gè)任務(wù),把 CPU 時(shí)間片出讓給其他任務(wù),然后當(dāng)這個(gè) IO 操作完成時(shí),通知 CPU 恢復(fù)后續(xù)代碼的執(zhí)行,實(shí)際上 CPU 大部分時(shí)間都是在做這種調(diào)度。所以并發(fā)編程可以最大限度榨取 CPU 的價(jià)值,提高程序的執(zhí)行效率和性能。

并發(fā)編程的常見(jiàn)實(shí)現(xiàn)

目前,主流的并發(fā)編程實(shí)現(xiàn)有以下幾種方式:

  • 多進(jìn)程。多進(jìn)程是在操作系統(tǒng)層面進(jìn)行并發(fā)的基本模式,同時(shí)也是開(kāi)銷最大的模式。在 Linux 平臺(tái)上,很多工具正是采用這種模式在工作,比如 PHP-FPM,它會(huì)有專門(mén)的主進(jìn)程負(fù)責(zé)網(wǎng)絡(luò)端口的監(jiān)聽(tīng)和連接管理,還會(huì)有多個(gè)工作進(jìn)程負(fù)責(zé)具體的請(qǐng)求處理。這種方法的好處在于簡(jiǎn)單、進(jìn)程間互不影響,壞處在于系統(tǒng)開(kāi)銷大,因?yàn)樗械倪M(jìn)程都是由內(nèi)核管理的,而且不同進(jìn)程的數(shù)據(jù)也是相互隔離的。

  • 多線程。多線程在大部分操作系統(tǒng)上都屬于系統(tǒng)層面的并發(fā)模式,也是我們使用最多的最有效的一種模式。目前,常見(jiàn)的幾乎所有工具都會(huì)使用這種模式,線程比進(jìn)程輕量級(jí),線程間可以共享數(shù)據(jù),開(kāi)銷要比多進(jìn)程小很多,但是依舊比較大,且在高并發(fā)模式下,效率會(huì)有影響,比如 C10K 問(wèn)題,即支持 1 萬(wàn)個(gè)并發(fā)連接需要一萬(wàn)個(gè)線程,這不但對(duì)系統(tǒng)資源有較高的要求,還對(duì) CPU 管理這些線程帶來(lái)巨大負(fù)擔(dān)。

  • 基于回調(diào)的非阻塞/異步 IO。為了解決 C10K 問(wèn)題,在很多高并發(fā)服務(wù)器開(kāi)發(fā)實(shí)踐中,都會(huì)通過(guò)事件驅(qū)動(dòng)的方式使用異步 IO,在這種模式下,一個(gè)線程可以維護(hù)多個(gè) Socket 連接,從而降低系統(tǒng)開(kāi)銷,保持服務(wù)器的持續(xù)運(yùn)轉(zhuǎn),它目前在 Node.js 中得到了很好的實(shí)踐,實(shí)際上 Nginx 也使用了這種方式。但是使用這種模式,編程比多線程要復(fù)雜,通常需要借助 Linux 底層的庫(kù)函數(shù)來(lái)實(shí)現(xiàn)。

  • 協(xié)程。協(xié)程(Coroutine)本質(zhì)上是一種用戶態(tài)線程,你可以把它看作輕量級(jí)的線程,不需要操作系統(tǒng)來(lái)進(jìn)行搶占式調(diào)度,系統(tǒng)開(kāi)銷極小,可以有效提高線程的任務(wù)并發(fā)性,避免多線程的缺點(diǎn)。使用協(xié)程的優(yōu)點(diǎn)是編程簡(jiǎn)單,結(jié)構(gòu)清晰;缺點(diǎn)是需要語(yǔ)言級(jí)別的支持,如果不支持,則需要用戶在程序中自行實(shí)現(xiàn)相應(yīng)的調(diào)度器。目前,原生支持協(xié)程的語(yǔ)言還很少,Go 語(yǔ)言就是其中這一,Go 語(yǔ)言中的協(xié)程稱作「goroutine」,并且使用語(yǔ)言名稱本身 go 做為協(xié)程的關(guān)鍵字,足見(jiàn)其在 Go 語(yǔ)言中的舉足輕重。PHP 的 Swoole 擴(kuò)展也是參考了 Go 協(xié)程的實(shí)現(xiàn)將其搬到 PHP 中。

傳統(tǒng)并發(fā)模式的缺陷

接下來(lái)我們先詮釋一下傳統(tǒng)并發(fā)模型的缺陷,之后再講解 goroutine 并發(fā)模型是如何解決這些缺陷的。如果你之前只是熟悉 PHP 編程,沒(méi)有接觸過(guò)并發(fā)編程,那么需要好好消化下這些概念,學(xué)習(xí)完 Go 并發(fā)編程再回去看 Swoole 的協(xié)程,就非常駕輕就熟了。由于多進(jìn)程比較消耗系統(tǒng)資源,且進(jìn)程間數(shù)據(jù)隔離,CPU 切換成本高,因此,傳統(tǒng)并發(fā)編程多以多線程為主,比如 Java 就是這么做的。下面我們重點(diǎn)探討多線程與協(xié)程的對(duì)比。

我們之前在 PHP 中編程多是串行思維,串行的事務(wù)具有確定性,比如我們想好了123,然后按照這個(gè)順序來(lái)編寫(xiě)代碼,代碼會(huì)嚴(yán)格按照這個(gè)設(shè)定的順序執(zhí)行,即使在某一個(gè)步驟阻塞了,也會(huì)一直等待阻塞代碼執(zhí)行完畢,再去執(zhí)行下一步的代碼。

多線程并發(fā)模式在這種確定性中引入了不確定性,比如我們?cè)仍O(shè)定的123,第2步是一個(gè)耗時(shí)操作,我們啟動(dòng)了一個(gè)新的線程來(lái)處理,這個(gè)時(shí)候就存在了兩個(gè)并發(fā)的線程,即原來(lái)的主線程和第2步啟動(dòng)的新線程,主線程繼續(xù)往后執(zhí)行,第2、3步的代碼并發(fā)執(zhí)行,這個(gè)時(shí)候不確定性就來(lái)了,我們不知道主線程執(zhí)行完畢的時(shí)候,新線程是否執(zhí)行完畢了,如果主線程執(zhí)行完畢退出應(yīng)用,可能導(dǎo)致新線程的中斷,或者我們?cè)诘?步的時(shí)候依賴第2步的某個(gè)返回結(jié)果,我們不知道啥時(shí)候能夠返回這個(gè)結(jié)果,如果第2、3步有相互依賴的變量,甚至可能出現(xiàn)死鎖,以及我們?nèi)绾卧谥骶€程中獲取新線程的異常和錯(cuò)誤信息并進(jìn)行相應(yīng)的處理,等等,這種不確定性給程序的行為帶來(lái)了意外和危害,也讓程序變得不可控。

不同的線程好比平行時(shí)空,我們需要通過(guò)線程間通信來(lái)告知不同線程目前各自運(yùn)行的狀態(tài)和結(jié)果,以便使程序可控,線程之間通信可以通過(guò)共享內(nèi)存的方式(參考 Swoole 中的 Swoole Table),即在不同線程中操作的是同一個(gè)內(nèi)存地址上存儲(chǔ)的值。為了保證共享內(nèi)存的有效性,需要采取很多措施,比如加鎖來(lái)避免死鎖或資源競(jìng)爭(zhēng),還是以上面的主線程和新線程為例,如果我們?cè)诘?步獲取了一個(gè)中間結(jié)果,第2步和第3步都要對(duì)這個(gè)中間結(jié)果進(jìn)行操作,如果不加鎖保證操作的原子性,很有可能產(chǎn)生臟數(shù)據(jù)。諸如此類的問(wèn)題在生產(chǎn)環(huán)境極有可能造成重大故障甚至事故,而且不易察覺(jué)和調(diào)試。

我們可以將線程加共享內(nèi)存的方式稱為「共享內(nèi)存系統(tǒng)」。為了解決共享內(nèi)存系統(tǒng)存在的問(wèn)題,計(jì)算機(jī)科學(xué)家們又提出了「消息傳遞系統(tǒng)」,所謂「消息傳遞系統(tǒng)」指的是將線程間共享狀態(tài)的各種操作都封裝在線程之間傳遞的消息中,這通常要求發(fā)送消息時(shí)對(duì)狀態(tài)進(jìn)行復(fù)制,并且在消息傳遞的邊界上交出這個(gè)狀態(tài)的所有權(quán)。從表明上來(lái)看,這個(gè)操作與「共享內(nèi)存系統(tǒng)」中執(zhí)行的通過(guò)加鎖實(shí)現(xiàn)原子更新操作相同,但從底層實(shí)現(xiàn)上來(lái)看則不同:一個(gè)對(duì)同一個(gè)內(nèi)存地址持有的值進(jìn)行操作,一個(gè)是從消息通道讀取數(shù)據(jù)并處理。由于需要執(zhí)行狀態(tài)復(fù)制操作,所以大多數(shù)消息傳遞的實(shí)現(xiàn)在性能上并不優(yōu)越,但線程中的狀態(tài)管理工作則會(huì)變得更加簡(jiǎn)單,這就有點(diǎn)像我們?cè)陂_(kāi)篇講 PHP 不支持并發(fā)編程提到的那樣,如果想讓編碼簡(jiǎn)單,性能就要做犧牲,如果想追求性能,代碼編寫(xiě)起來(lái)就比較費(fèi)勁,這也是我們?yōu)槭裁赐ǔ2粫?huì)直接通過(guò)事件驅(qū)動(dòng)的異步 IO 來(lái)實(shí)現(xiàn)并發(fā)編程一樣,因?yàn)檫@涉及到直接調(diào)用操作系統(tǒng)底層的庫(kù)函數(shù)(select、epoll、libevent 等)來(lái)實(shí)現(xiàn),非常復(fù)雜。

注:最早被廣泛應(yīng)用的「消息傳遞系統(tǒng)」是由 C. A. R. Hoare 在他的 Communicating Sequential Processes 中提出的,在 CSP 系統(tǒng)中,所有的并發(fā)操作都是通過(guò)獨(dú)立線程以異步運(yùn)行的方式來(lái)實(shí)現(xiàn)的。這些線程必須通過(guò)在彼此之間發(fā)送消息,從而向另一個(gè)線程請(qǐng)求信息或者將信息提供給另一個(gè)線程。

Go 語(yǔ)言協(xié)程支持

與傳統(tǒng)的系統(tǒng)級(jí)線程和進(jìn)程相比,協(xié)程的最大優(yōu)勢(shì)在于輕量級(jí)(可以看作用戶態(tài)的輕量級(jí)線程),我們可以輕松創(chuàng)建上百萬(wàn)個(gè)協(xié)程而不會(huì)導(dǎo)致系統(tǒng)資源衰竭,而線程和進(jìn)程通常最多也不能超過(guò) 1 萬(wàn)個(gè)(C10K問(wèn)題)。多數(shù)語(yǔ)言在語(yǔ)法層面并不直接支持協(xié)程,而是通過(guò)庫(kù)的方式支持,比如 PHP 的 Swoole 擴(kuò)展庫(kù),但用庫(kù)的方式支持的功能通常并不完整,比如僅僅提供輕量級(jí)線程的創(chuàng)建、銷毀與切換等能力。如果在這樣的輕量級(jí)線程中調(diào)用一個(gè)同步 IO 操作,比如網(wǎng)絡(luò)通信、本地文件讀寫(xiě),都會(huì)阻塞其他的并發(fā)執(zhí)行輕量級(jí)線程,從而無(wú)法真正達(dá)到輕量級(jí)線程本身期望達(dá)到的目標(biāo)。

Go 語(yǔ)言在語(yǔ)言級(jí)別支持協(xié)程,稱之為 goroutine。Go 語(yǔ)言標(biāo)準(zhǔn)庫(kù)提供的所有系統(tǒng)調(diào)用操作(當(dāng)然也包括所有同步 IO 操作),都有協(xié)程的身影。協(xié)程間的切換管理不依賴于系統(tǒng)的線程和進(jìn)程,也不依賴于 CPU 的核心數(shù)量,這讓我們?cè)?Go 語(yǔ)言中通過(guò)協(xié)程實(shí)現(xiàn)并發(fā)編程變得非常簡(jiǎn)單。

Go 語(yǔ)言的協(xié)程系統(tǒng)是基于「消息傳遞系統(tǒng)」實(shí)現(xiàn)的,在 Go 語(yǔ)言的編程哲學(xué)中,創(chuàng)始人 Rob Pike 推介「Don’t communicate by sharing memory, share memory by communicating(不要通過(guò)共享內(nèi)存來(lái)通信,而應(yīng)該通過(guò)通信來(lái)共享內(nèi)存)」,這正是「消息傳遞系統(tǒng)」的精髓所在。Go 語(yǔ)言中的 goroutine 和用于傳遞協(xié)程間消息的 channel 一起,共同構(gòu)筑了 Go 語(yǔ)言協(xié)程系統(tǒng)的基石。

go適合做什么

go是golang的簡(jiǎn)稱,而golang可以做服務(wù)器端開(kāi)發(fā),且golang很適合做日志處理、數(shù)據(jù)打包、虛擬機(jī)處理、數(shù)據(jù)庫(kù)代理等工作。在網(wǎng)絡(luò)編程方面,它還廣泛應(yīng)用于web應(yīng)用、API應(yīng)用等領(lǐng)域。

看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝您對(duì)億速云的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI