深入淺析python 協(xié)程與go協(xié)程的區(qū)別
進(jìn)程、線程和協(xié)程
進(jìn)程的定義:
進(jìn)程,是計(jì)算機(jī)中已運(yùn)行程序的實(shí)體。程序本身只是指令、數(shù)據(jù)及其組織形式的描述,進(jìn)程才是程序的真正運(yùn)行實(shí)例。
線程的定義:
操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。
進(jìn)程和線程的關(guān)系:
一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。
CPU的最小調(diào)度單元是線程不是進(jìn)程,所以單進(jìn)程多線程也可以利用多核CPU.
協(xié)程的定義:
協(xié)程通過(guò)在線程中實(shí)現(xiàn)調(diào)度,避免了陷入內(nèi)核級(jí)別的上下文切換造成的性能損失,進(jìn)而突破了線程在IO上的性能瓶頸。
協(xié)程和線程的關(guān)系
協(xié)程是在語(yǔ)言層面實(shí)現(xiàn)對(duì)線程的調(diào)度,避免了內(nèi)核級(jí)別的上下文消耗。
python協(xié)程與調(diào)度
Python的協(xié)程源于yield指令。yield有兩個(gè)功能:
•yield item用于產(chǎn)出一個(gè)值,反饋給next()的調(diào)用方。
•作出讓步,暫停執(zhí)行生成器,讓調(diào)用方繼續(xù)工作,直到需要使用另一個(gè)值時(shí)再調(diào)用next()。
import asyncio import datetime async def display_date(): loop = asyncio.get_running_loop() end_time = loop.time() + 5.0 while True: print(datetime.datetime.now()) if (loop.time() + 1.0) >= end_time: break await asyncio.sleep(1) asyncio.run(display_date())
協(xié)程是對(duì)線程的調(diào)度,yield類(lèi)似惰性求值方式可以視為一種流程控制工具,實(shí)現(xiàn)協(xié)作式多任務(wù),在Python3.5正式引入了 Async/Await表達(dá)式,使得協(xié)程正式在語(yǔ)言層面得到支持和優(yōu)化,大大簡(jiǎn)化之前的yield寫(xiě)法。
線程是內(nèi)核進(jìn)行搶占式的調(diào)度的,這樣就確保了每個(gè)線程都有執(zhí)行的機(jī)會(huì)。
而 coroutine 運(yùn)行在同一個(gè)線程中,由語(yǔ)言的運(yùn)行時(shí)中的 EventLoop(事件循環(huán))來(lái)進(jìn)行調(diào)度。
和大多數(shù)語(yǔ)言一樣,在 Python 中,協(xié)程的調(diào)度是非搶占式的,也就是說(shuō)一個(gè)協(xié)程必須主動(dòng)讓出執(zhí)行機(jī)會(huì),其他協(xié)程才有機(jī)會(huì)運(yùn)行。
讓出執(zhí)行的關(guān)鍵字就是 await。也就是說(shuō)一個(gè)協(xié)程如果阻塞了,持續(xù)不讓出 CPU,那么整個(gè)線程就卡住了,沒(méi)有任何并發(fā)。
PS: 作為服務(wù)端,event loop最核心的就是IO多路復(fù)用技術(shù),所有來(lái)自客戶端的請(qǐng)求都由IO多路復(fù)用函數(shù)來(lái)處理;作為客戶端,
event loop的核心在于利用Future對(duì)象延遲執(zhí)行,并使用send函數(shù)激發(fā)協(xié)程,掛起,等待服務(wù)端處理完成返回后再調(diào)用CallBack函數(shù)繼續(xù)下面的流程
Go的協(xié)程是天生在語(yǔ)言層面支持,和Python類(lèi)似都是采用了關(guān)鍵字,而Go語(yǔ)言使用了go這個(gè)關(guān)鍵字,可能是想表明協(xié)程是Go語(yǔ)言中最重要的特性。
go協(xié)程之間的通信,Go采用了channel關(guān)鍵字。
Go實(shí)現(xiàn)了兩種并發(fā)形式:
•多線程共享內(nèi)存。如Java或者C++等在多線程中共享數(shù)據(jù)(例如數(shù)組、Map、或者某個(gè)結(jié)構(gòu)體或?qū)ο螅┑臅r(shí)候,通過(guò)鎖來(lái)訪問(wèn).
•Go語(yǔ)言特有的,也是Go語(yǔ)言推薦的:CSP(communicating sequential processes)并發(fā)模型。
Go的CSP并發(fā)模型實(shí)現(xiàn):M, P, G :
package main
import (
"fmt"
)
//Go 協(xié)程(goroutines)和協(xié)程(coroutines)
//Go 協(xié)程意味著并行(或者可以以并行的方式部署),協(xié)程一般來(lái)說(shuō)不是這樣的
//Go 協(xié)程通過(guò)通道來(lái)通信;協(xié)程通過(guò)讓出和恢復(fù)操作來(lái)通信
// 進(jìn)程退出時(shí)不會(huì)等待并發(fā)任務(wù)結(jié)束,可用通道(channel)阻塞,然后發(fā)出退出信號(hào)
func main() {
jobs := make(chan int)
done := make(chan bool) // 結(jié)束標(biāo)志
go func() {
for {
j, more := <-jobs // 利用more這個(gè)值來(lái)判斷通道是否關(guān)閉,如果關(guān)閉了,那么more的值為false,并且通知給通道done
fmt.Println("----->:", j, more)
if more {
fmt.Println("received job", j)
} else {
fmt.Println("end received jobs")
done <- true
return
}
}
}()
go func() {
for j := 1; j <= 3; j++ {
jobs <- j
fmt.Println("sent job", j)
}
close(jobs) // 寫(xiě)完最后的數(shù)據(jù),緊接著就close掉
fmt.Println("close(jobs)")
}()
fmt.Println("sent all jobs")
<-done // 讓main等待全部協(xié)程完成工作
}
通過(guò)在函數(shù)調(diào)用前使用關(guān)鍵字 go,我們即可讓該函數(shù)以 goroutine 方式執(zhí)行。goroutine 是一種 比線程更加輕盈、更省資源的協(xié)程。
Go 語(yǔ)言通過(guò)系統(tǒng)的線程來(lái)多路派遣這些函數(shù)的執(zhí)行,使得 每個(gè)用 go 關(guān)鍵字執(zhí)行的函數(shù)可以運(yùn)行成為一個(gè)單位協(xié)程。
當(dāng)一個(gè)協(xié)程阻塞的時(shí)候,調(diào)度器就會(huì)自 動(dòng)把其他協(xié)程安排到另外的線程中去執(zhí)行,從而實(shí)現(xiàn)了程序無(wú)等待并行化運(yùn)行。
而且調(diào)度的開(kāi)銷(xiāo)非常小,一顆 CPU 調(diào)度的規(guī)模不下于每秒百萬(wàn)次,這使得我們能夠創(chuàng)建大量的 goroutine,
從而可以很輕松地編寫(xiě)高并發(fā)程序,達(dá)到我們想要的目的。 ---- 某書(shū)
協(xié)程的4種狀態(tài)
•Pending
•Running
•Done
•Cacelled
和系統(tǒng)線程之間的映射關(guān)系
go的協(xié)程本質(zhì)上還是系統(tǒng)的線程調(diào)用,而Python中的協(xié)程是eventloop模型實(shí)現(xiàn),所以雖然都叫協(xié)程,但并不是一個(gè)東西.
Python 中的協(xié)程是嚴(yán)格的 1:N 關(guān)系,也就是一個(gè)線程對(duì)應(yīng)了多個(gè)協(xié)程。雖然可以實(shí)現(xiàn)異步I/O,但是不能有效利用多核(GIL)。
而 Go 中是 M:N 的關(guān)系,也就是 N 個(gè)協(xié)程會(huì)映射分配到 M 個(gè)線程上,這樣帶來(lái)了兩點(diǎn)好處:
•多個(gè)線程能分配到不同核心上,CPU 密集的應(yīng)用使用 goroutine 也會(huì)獲得加速.
•即使有少量阻塞的操作,也只會(huì)阻塞某個(gè) worker 線程,而不會(huì)把整個(gè)程序阻塞。
PS: Go中很少提及線程或進(jìn)程,也就是因?yàn)樯厦娴脑?
兩種協(xié)程對(duì)比:
•async是非搶占式的,一旦開(kāi)始采用 async 函數(shù),那么你整個(gè)程序都必須是 async 的,不然總會(huì)有阻塞的地方(一遇阻塞對(duì)于沒(méi)有實(shí)現(xiàn)異步特性的庫(kù)就無(wú)法主動(dòng)讓調(diào)度器調(diào)度其他協(xié)程了),也就是說(shuō) async 具有傳染性。
•Python 整個(gè)異步編程生態(tài)的問(wèn)題,之前標(biāo)準(zhǔn)庫(kù)和各種第三方庫(kù)的阻塞性函數(shù)都不能用了,requests 不能用了,redis.py 不能用了,甚至 open 函數(shù)都不能用了。所以 Python 協(xié)程的最大問(wèn)題不是不好用,而是生態(tài)環(huán)境不好。
•goroutine 是 go 與生俱來(lái)的特性,所以幾乎所有庫(kù)都是可以直接用的,避免了 Python 中需要把所有庫(kù)重寫(xiě)一遍的問(wèn)題。
•Goroutine 中不需要顯式使用 await 交出控制權(quán),但是 Go 也不會(huì)嚴(yán)格按照時(shí)間片去調(diào)度 goroutine,而是會(huì)在可能阻塞的地方插入調(diào)度。Goroutine 的調(diào)度可以看做是半搶占式的。
PS: python異步庫(kù)列表 [https://github.com/timofurrer/awesome-asyncio]
--------------------------------------------------------------------------------
Do not communicate by sharing memory; instead, share memory by communicating.(不要以共享內(nèi)存的方式來(lái)通信,相反,要通過(guò)通信來(lái)共享內(nèi)存) -- CSP并發(fā)模型
--------------------------------------------------------------------------------
擴(kuò)展與總結(jié)
erlang和golang都是采用了CSP(Communicating Sequential Processes)模式(Python中的協(xié)程是eventloop模型)
但是erlang是基于進(jìn)程的消息通信,go是基于goroutine和channel的通信。
Python和Go都引入了消息調(diào)度系統(tǒng)模型,來(lái)避免鎖的影響和進(jìn)程/線程開(kāi)銷(xiāo)大的問(wèn)題。
協(xié)程從本質(zhì)上來(lái)說(shuō)是一種用戶態(tài)的線程,不需要系統(tǒng)來(lái)執(zhí)行搶占式調(diào)度,而是在語(yǔ)言層面實(shí)現(xiàn)線程的調(diào)度。
因?yàn)閰f(xié)程不再使用共享內(nèi)存/數(shù)據(jù),而是使用通信來(lái)共享內(nèi)存/鎖,因?yàn)樵谝粋€(gè)超級(jí)大系統(tǒng)里具有無(wú)數(shù)的鎖,
共享變量等等會(huì)使得整個(gè)系統(tǒng)變得無(wú)比的臃腫,而通過(guò)消息機(jī)制來(lái)交流,可以使得每個(gè)并發(fā)的單元都成為一個(gè)獨(dú)立的個(gè)體,
擁有自己的變量,單元之間變量并不共享,對(duì)于單元的輸入輸出只有消息。
開(kāi)發(fā)者只需要關(guān)心在一個(gè)并發(fā)單元的輸入與輸出的影響,而不需要再考慮類(lèi)似于修改共享內(nèi)存/數(shù)據(jù)對(duì)其它程序的影響。
相關(guān)文章
出現(xiàn)module 'queue' has no attrib
這篇文章主要介紹了出現(xiàn)module 'queue' has no attribute 'Queue'問(wèn)題的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04
Django自動(dòng)注冊(cè)tasks及使用方式
這篇文章主要為大家介紹了Django自動(dòng)注冊(cè)tasks及使用方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
500行代碼使用python寫(xiě)個(gè)微信小游戲飛機(jī)大戰(zhàn)游戲
這篇文章主要介紹了500行代碼使用python寫(xiě)個(gè)微信小游戲飛機(jī)大戰(zhàn)游戲,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10
Python游戲開(kāi)發(fā)實(shí)例之graphics實(shí)現(xiàn)AI五子棋
五子棋是經(jīng)典的棋牌類(lèi)游戲,很多人都玩過(guò),那么如何用Python實(shí)現(xiàn)五子棋呢,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
利用tkinter實(shí)現(xiàn)下拉框聯(lián)動(dòng)
這篇文章主要介紹了利用tkinter實(shí)現(xiàn)下拉框聯(lián)動(dòng)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01
postman模擬訪問(wèn)具有Session的post請(qǐng)求方法
今天小編就為大家分享一篇postman模擬訪問(wèn)具有Session的post請(qǐng)求方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-07-07

