go語言-csp模型-併發通道
【前言】go語言的併發機制以及它所使用的CSP併發模型
一、CSP併發模型
CSP模型是上個世紀七十年代提出的,用於描述兩個獨立的併發實體通過共享的通訊 channel(管道)進行通訊的併發模型。 CSP中channel是第一類物件,它不關注傳送訊息的實體,而關注與傳送訊息時使用的channel。
1、Golang CSP
Golang 就是借用CSP模型的一些概念為之實現併發進行理論支援,其實從實際上出發,go語言並沒有,完全實現了CSP模型的所有理論,僅僅是借用了 process和channel這兩個概念。process是在go語言上的表現就是 goroutine 是實際併發執行的實體,每個實體之間是通過channel通訊來實現資料共享。
2、Channel
Golang中使用 CSP中 channel 這個概念。channel 是被單獨建立並且可以在程序之間傳遞,它的通訊模式類似於 boss-worker 模式的,一個實體通過將訊息傳送到channel 中,然後又監聽這個 channel 的實體處理,兩個實體之間是匿名的,這個就實現實體中間的解耦,其中 channel 是同步的一個訊息被髮送到 channel 中,最終是一定要被另外的實體消費掉的,在實現原理上其實是一個阻塞的訊息佇列。
3、Goroutine
Goroutine 是實際併發執行的實體,它底層是使用協程(coroutine)實現併發,coroutine是一種執行在使用者態的使用者執行緒(協程)
- 使用者空間 避免了核心態和使用者態的切換導致的成本
- 可以由語言和框架層進行排程
- 更小的棧空間允許建立大量的例項(需要棧空間4-5kb)
可以看到第二條 使用者空間執行緒的排程不是由作業系統來完成的,像在java 1.3中使用的greenthread的是由JVM統一排程的(後java已經改為核心執行緒),還有在ruby中的fiber(半協程) 是需要在重新中自己進行排程的,而goroutine是在golang層面提供了排程器,並且對網路IO庫進行了封裝,遮蔽了複雜的細節,對外提供統一的語法關鍵字支援,簡化了併發程式編寫的成本。
4、小結
goroutine執行在相同的地址空間,因此訪問共享記憶體必須做好同步。goroutine通過通道(channel)來通訊,而不是共享記憶體。使用channel用於多個goroutine通訊,內部實現同步。channel底層是一個類似map的資料結構的引用。即,引用傳參。
二、Goroutine 排程器
golang使用goroutine做為最小的執行單位,但是這個執行單位還是在使用者空間,實際上最後被處理器執行的還是核心中的執行緒,使用者執行緒和核心執行緒的排程方法有:
-
N:1 多個使用者執行緒對應一個核心執行緒
-
1:1 一個使用者執行緒對應一個核心執行緒
-
M:N 使用者執行緒和核心執行緒是多對多的對應關係
golang 通過為goroutine提供語言層面的排程器,來實現了高效率的M:N執行緒對應關係
排程示意圖中
- M:是核心執行緒
- P : 是排程協調,用於協調M和G的執行,核心執行緒只有拿到了 P才能對goroutine繼續排程執行,一般都是通過限定P的個數來控制golang的併發度
- G : 是待執行的goroutine,包含這個goroutine的棧空間
- Gn : 灰色背景的Gn 是已經掛起的goroutine,它們被新增到了執行佇列中,然後需要等待網路IO的goroutine,當P通過 epoll查詢到特定的fd的時候,會重新排程起對應的,正在掛起的goroutine。
Golang為了排程的公平性,在排程器加入了steal working 演算法 ,在一個P自己的執行佇列,處理完之後,它會先到全域性的執行佇列中偷G進行處理,如果沒有的話,再會到其他P的執行佇列中搶G來進行處理。
總結
Golang實現了 CSP 併發模型做為併發基礎,底層使用goroutine做為併發實體,goroutine非常輕量級可以建立幾十萬個實體。實體間通過 channel(底層引用資料) 繼續匿名訊息傳遞使之解耦,在語言層面實現了自動排程,這樣遮蔽了很多內部細節,對外提供簡單的語法關鍵字,大大簡化了併發程式設計的思維轉換和管理執行緒的複雜性。