這裏先給出一些經常使用的知識點簡要說明,以便理解後面的文章內容。html
進程的定義:python
進程,是計算機中已運行程序的實體。程序自己只是指令、數據及其組織形式的描述,進程纔是程序的真正運行實例。git
線程的定義:github
操做系統可以進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運做單位。golang
進程和線程的關係:redis
一條線程指的是進程中一個單一順序的控制流,一個進程中能夠併發多個線程,每條線程並行執行不一樣的任務。
CPU的最小調度單元是線程不是進程,因此單進程多線程也能夠利用多核CPU.編程
協程的定義:數組
協程經過在線程中實現調度,避免了陷入內核級別的上下文切換形成的性能損失,進而突破了線程在IO上的性能瓶頸。多線程
協程和線程的關係併發
協程是在語言層面實現對線程的調度,避免了內核級別的上下文消耗。
Python的協程源於yield指令。yield有兩個功能:
import asyncio async def compute(x, y): print("Compute %s + %s ..." % (x, y)) await asyncio.sleep(x + y) return x + y async def print_sum(x, y): result = await compute(x, y) print("%s + %s = %s" % (x, y, result)) loop = asyncio.get_event_loop() tasks = [print_sum(1, 2), print_sum(3, 4)] loop.run_until_complete(asyncio.wait(tasks)) loop.close()
協程是對線程的調度,yield相似惰性求值方式能夠視爲一種流程控制工具,實現協做式多任務,在Python3.5正式引入了async/await
表達式,使得協程正式在語言層面獲得支持和優化,大大簡化以前的yield寫法。
線程是內核進行搶佔式的調度的,這樣就確保了每一個線程都有執行的機會。
而 coroutine
運行在同一個線程中,由語言的運行時中的 EventLoop
(事件循環)來進行調度。
和大多數語言同樣,在 Python 中,協程的調度是非搶佔式的,也就是說一個協程必須主動讓出執行機會,其餘協程纔有機會運行。
讓出執行的關鍵字就是 await。也就是說一個協程若是阻塞了,持續不讓出 CPU,那麼整個線程就卡住了,沒有任何併發。
簡而言之,任什麼時候候只有一個協程正在運行。
PS: 做爲服務端,event loop最核心的就是IO多路複用技術,全部來自客戶端的請求都由IO多路複用函數來處理;做爲客戶端,event loop的核心在於利用Future對象延遲執行,並使用send函數激發協程,掛起,等待服務端處理完成返回後再調用CallBack函數繼續下面的流程
Go天生在語言層面支持,和Python相似都是採用了關鍵字,而Go語言使用了go這個關鍵字,多是想代表協程是Go語言中最重要的特性。
go協程之間的通訊,Go採用了channel關鍵字。
Go實現了兩種併發形式:
Go的CSP併發模型實現:M, P, G : [https://www.cnblogs.com/sunsk...]
package main import ( "fmt" ) //Go 協程(goroutines)和協程(coroutines) //Go 協程意味着並行(或者能夠以並行的方式部署),協程通常來講不是這樣的 //Go 協程經過通道來通訊;協程經過讓出和恢復操做來通訊 // 進程退出時不會等待併發任務結束,可用通道(channel)阻塞,而後發出退出信號 func main() { jobs := make(chan int) done := make(chan bool) // 結束標誌 go func() { for { j, more := <-jobs // 利用more這個值來判斷通道是否關閉,若是關閉了,那麼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) // 寫完最後的數據,緊接着就close掉 fmt.Println("close(jobs)") }() fmt.Println("sent all jobs") <-done // 讓main等待所有協程完成工做 }
經過在函數調用前使用關鍵字go
,咱們便可讓該函數以 goroutine
方式執行。goroutine
是一種 比線程更加輕盈、更省資源的協程。
Go 語言經過系統的線程來多路派遣這些函數的執行,使得 每一個用 go 關鍵字執行的函數能夠運行成爲一個單位協程。
當一個協程阻塞的時候,調度器就會自 動把其餘協程安排到另外的線程中去執行,從而實現了程序無等待並行化運行。
並且調度的開銷很是小,一顆 CPU 調度的規模不下於每秒百萬次,這使得咱們可以建立大量的 goroutine
,從而能夠很輕鬆地編寫高併發程序,達到咱們想要的目的。 ---- 某書
協程的4種狀態
和系統線程之間的映射關係
go的協程本質上仍是系統的線程調用,而Python中的協程是eventloop模型實現,因此雖然都叫協程,但並非一個東西.
Python 中的協程是嚴格的 1:N 關係,也就是一個線程對應了多個協程。雖然能夠實現異步I/O,可是不能有效利用多核(GIL)。
而 Go 中是 M:N 的關係,也就是 N 個協程會映射分配到 M 個線程上,這樣帶來了兩點好處:
PS: Go中不多說起線程或進程,也就是由於上面的緣由.
async
是非搶佔式的,一旦開始採用 async
函數,那麼你整個程序都必須是 async
的,否則總會有阻塞的地方(一遇阻塞對於沒有實現異步特性的庫就沒法主動讓調度器調度其餘協程了),也就是說 async
具備傳染性。goroutine
是 Go 與生俱來的特性,因此幾乎全部庫都是能夠直接用的,避免了 Python 中須要把全部庫重寫一遍的問題。goroutine
中不須要顯式使用await
交出控制權,可是 Go 也不會嚴格按照時間片去調度 goroutine
,而是會在可能阻塞的地方插入調度。goroutine
的調度能夠看作是半搶佔式的。Do not communicate by sharing memory; instead, share memory by communicating.(不要以共享內存的方式來通訊,相反,要經過通訊來共享內存) -- CSP併發模型
erlang和golang都是採用了CSP(Communicating Sequential Processes)模式(Python中的協程是eventloop模型) 可是erlang是基於進程的消息通訊,go是基於goroutine和channel的通訊。 Python和Go都引入了消息調度系統模型,來避免鎖的影響和進程/線程開銷大的問題。 協程從本質上來講是一種用戶態的線程,不須要系統來執行搶佔式調度,而是在語言層面實現線程的調度。 由於協程再也不使用共享內存/數據,而是使用通訊來共享內存/鎖,由於在一個超級大系統裏具備無數的鎖, 共享變量等等會使得整個系統變得無比的臃腫,而經過消息機制來交流,可使得每一個併發的單元都成爲一個獨立的個體, 擁有本身的變量,單元之間變量並不共享,對於單元的輸入輸出只有消息。 開發者只須要關心在一個併發單元的輸入與輸出的影響,而不須要再考慮相似於修改共享內存/數據對其它程序的影響。