[TOC]shell
以前咱們分享了網絡編程,今天咱們來看看GO的併發編程分享,咱們先來看看他是個啥編程
指在一臺處理器上同時處理多個任務緩存
此處說的同時,可不是同一個時間一塊兒手拉手作同一件事情安全
併發是在同一實體上的多個事件,而這個事件在同一時間間隔發生的,同一個時間段,有多個任務執行,但是同一個時間點,只有一個任務在執行網絡
隨着互聯網的普及,互聯網用戶人數原來越多,這對系統的性能帶來了巨大的挑戰。多線程
咱們要經過各類方式來高效利用硬件的性能(壓榨),從而提升系統的性能進而提高用戶體驗,提高團隊或者企業的競爭力。併發
併發是爲了解決什麼問題?目的是啥?函數
是充分的利用好處理器的每個核,以達到最高的處理性能,儘量的運用好每一塊磚高併發
但是因爲如今咱們使用的CPU,內存,IO三者之間速度不盡相同性能
咱們爲了提升系統性能,計算機系統會將這三者速度進行平衡,以達到最優的效果,都有以下措施:
說到進程和線程,他們都是幹啥的呢,我們順帶說一下?
是 系統進行資源分配和調度的一個獨立單位。
是 CPU 調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。
講到併發編程不得不說併發和並行有啥區別?是否是老是有小夥伴弄不清楚他們究竟是啥區別,好像同樣,又好像不同
一言蔽之,區別以下:
併發
多線程程序在一個核的 CPU 上運行
並行
多線程程序在多個核的 CPU 上運行
併發就像多個小夥伴跑接力,同一個時間點只會有一個小夥伴在跑,互相有影響
並行就像是多個小夥伴同一個起點一塊兒跑,互不干擾
咱們須要記住一點,再強調一波:
併發不是並行
併發主要由切換時間片來實現"同時"運行
並行則是直接利用多核實現多線程的運行,
在 GO 能夠設置使用核數,以發揮多核計算機的能力,不過設置核數都是依賴於硬件的
那麼,講到GO的併發編程,就必須上咱們的主角,那就是協程
協程是一種程序組件
是由子例程(過程、函數、例程、方法、子程序)的概念泛化而來的
子例程只有一個入口點且只返回一次,而協程容許多個入口點,能夠在指定位置掛起和恢復執行。
協程和線程分別有啥特色嘞
獨立的棧空間,共享堆空間,調度由用戶本身控制
本質上有點相似於用戶級線程,這些用戶級線程的調度也是本身實現的。
一個線程上能夠跑多個協程,協程是輕量級的線程。
goroutine
奉行經過通訊來共享內存4~5KB
的棧內存佔用,而且因爲 GO 實現機制而大幅減小的建立和銷燬開銷goroutine
咱們寫C/C++
的時候,咱們必然也是要實現併發編程
咱們一般須要本身維護一個線程池,而且須要本身去包裝一個又一個的任務,同時須要本身去調度線程執行任務並維護上下文切換
且作線程池的時候,咱們須要本身作一個線程管理的角色,靈活動態壓縮和擴容
但是能不能有這樣一種機制,咱們只須要定義多個任務,讓系統去幫助咱們把這些任務分配到CPU上實現併發執行
GO裏面就正好有這樣的機制
goroutine
的概念相似於線程
goroutine
是由Go的運行時(runtime)調度和管理的
Go程序會智能地將 goroutine
中的任務合理地分配給每一個CPU
Go 在語言層面已經內置了調度和上下文切換的機制
寫 GO 比較爽的一個地方是:
在GO裏面,你不須要去本身寫進程、線程、協程
咱們可使用 goroutine 包
咱們須要讓某個任務併發執行的時候,只須要把這個任務包裝成一個函數
專門開啓一個 goroutine 協程 去執行這個函數就能夠了 , GO一個協程,很方便
一個 goroutine 一定對應一個函數,能夠建立多個 goroutine 去執行相同的函數,只是多個協程都是作同一個事情罷了
咱們先來使用一下協程,再來拋磚引玉,適當的分享一下
func Hi() { fmt.Println("this is Hi Goroutine!") } func main() { Hi() fmt.Println("main goroutine!") }
咱們通常調用函數是如上這個樣子的,效果以下
this is Hi Goroutine! main goroutine!
其實咱們調用協程的話,也與上述相似
咱們可使用 go 後面加上函數名字,來開闢一個協程,專門作函數須要執行的事情
func main() { go Hi() // 啓動一個goroutine 協程 去執行 Hi 函數 fmt.Println("main goroutine!")
實際效果咱們能夠看到,程序只打印了 main goroutine!
main goroutine!
在程序啓動的時候,Go 程序就會爲 main() 函數建立一個默認的 goroutine 協程
當 main() 函數返回的時候,剛開闢的另一個 goroutine 協程 就結束了
全部在 main() 函數中啓動的 goroutine 協程 會一同結束,老大死了,其他的傀儡也灰飛煙滅了
咱們也可讓主協程等等必定子協程,待子協程處理完本身的事情,退出後,主協程再本身退出,這和咱們寫C/C++的進程 和 線程的時候,相似
簡單的,咱們可使用 time.sleep
函數來讓主協程阻塞等待
咱們也可使用 上述提到的 使用 select{} 來達到目的
固然也有其餘的方式,後續文章會慢慢的分享到
那麼多個協程又是怎麼玩的呢?
咱們使用 sync.WaitGroup 來實現goroutine 協程的同步
package main import ( "fmt" "sync" ) var myWg sync.WaitGroup func Hi(i int) { // goroutine 協程 結束就 記錄 -1 defer myWg.Done() fmt.Println("Hello Goroutine! the ", i) } func main() { for i := 0; i < 10; i++ { // 啓動一個goroutine 協程 就記錄 +1 myWg.Add(1) go Hi(i) } // 等待全部記錄 的goroutine 協程 都結束 myWg.Wait() }
會有以下輸出,每個協程打印的數字並非按照順序來的:
Hello Goroutine! the 9 Hello Goroutine! the 4 Hello Goroutine! the 2 Hello Goroutine! the 3 Hello Goroutine! the 6 Hello Goroutine! the 5 Hello Goroutine! the 7 Hello Goroutine! the 8 Hello Goroutine! the 1 Hello Goroutine! the 0
仍是一樣的, 若是是主協程先退出,那麼子協程還行繼續運行嗎?
毋庸置疑,主協程退出,子協程也會跟着退出
分享以下幾個點
通常都有固定的棧內存(一般爲2MB),goroutine 的棧不是固定的,goroutine 的棧大小能夠擴展到1GB
這就不得不提 GPM
GPM是Go語言運行時(runtime)層面實現的,咱們先簡單瞭解一下GPM分別表明啥
G
就是個 goroutine ,裏面除了存放本 goroutine 信息外 還有與所在P的綁定等信息
P
Processor 管理着一組 goroutine 隊列
P 裏面會存儲當前 goroutine 運行的上下文環境(函數指針,堆棧地址及地址邊界)
P 會對本身管理的 goroutine 隊列作一些調度(好比把佔用CPU時間較長的 goroutine 暫停、運行後續的 goroutine)
當本身的隊列消費完了就去全局隊列裏取,若是全局隊列裏也消費完了會去其餘P的隊列裏搶任務。
M(machine)
是 Go 運行時(runtime)對操做系統內核線程的虛擬
M 與內核線程通常是一一映射的關係, 一個 groutine 最終是要放到 M上執行
這裏的 P 與 M 通常也是一一對應的
P 管理着一組G 掛載在 M 上運行
當一個 G 長久阻塞在一個 M 上時,runtime 會新建一個M,
阻塞 G 所在的 P 會把其餘的 G 掛載在新建的M上
這個時候,當舊的 G 阻塞完成或者認爲其已經掛了的話,就會回收舊的 M
還有一點
P 的個數是經過 runtime.GOMAXPROCS
設定(最大256),這個數字也依賴於本身的硬件,在併發量大的時候會增長一些 P 和 M ,但不會太多
關於GO的併發編程知識點涉及面多,對於他的調度原理,真實感興趣的話,能夠看上述提到的GO併發涉及的知識點,一點一點的深刻下去,今天就到這裏,大致瞭解GO協程的使用
朋友們,你的支持和鼓勵,是我堅持分享,提升質量的動力
好了,本次就到這裏,下一次GO的鎖和原子操做分享
技術是開放的,咱們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。
我是小魔童哪吒,歡迎點贊關注收藏,下次見~