網站首頁 編程語言 正文
前言:
協程(coroutine)是 Go 語言最大的特色之一,goroutine 的實現其實是通過協程。
協程的概念
協程一詞最早出現在 1963 年發表的論文中,該論文的作者為美國計算機科學家 Melvin E.Conway。著名的康威定律:“設計系統的架構受制于產生這些設計的組織的溝通結構。” 也是這個作者。
協程是一種用戶態的輕量級線程,可以想成一個線程里面可以有多個協程,而協程的調度完全由用戶控制,協程也會有自己的 registers、context、stack 等等,并且由協程的調度器來控制說目前由哪個協程執行,哪個協程要被 block 住。
而相對于 Thread 及 Process 的調度,則是由 CPU 內核去進行調度,因此操作系統其實會有所謂許多的調度算法,并且可以進行搶占式調度,可以主動搶奪執行的控制權。
反之,協程是不行的,只能進行非搶占式的調度。 可以理解成,如果 coroutine 被 block 住,則會在用戶態直接切換另外一個 coroutine 給此 thread 繼續執行,這樣其他 coroutine 就不會被 block 住,讓資源能夠有效的被利用,借此實現 Concurrent 的概念。
協程與線程
線程?是 CPU 調度的基本單位,多個線程可以通過共享進程的資源,通過共享內存等方式來進行線程間通信。
協程?可理解為輕量級線程,與線程相比,協程不受操作系統系統調度,協程調度由用戶應用程序提供,協程調度器按照調度策略把協程調度到線程中運行。
- 協程只需花幾 KB 就可以被創立,線程則需要幾 MB 的內存才能創立
- 切換開銷方面,協程遠遠低于線程,切換的速度也因此大幅提升
goroutine 的誕生
Golang 語言的 goroutine 其實就是協程,特別的是在語言層面直接原生支持創立協程,并在 runtime、系統調用等多方面對 goroutine 調度進行封裝及處理。
相對于 Java 的建立線程,操作系統是會直接建立一個線程與其對應,而多個線程的間互相切換需要通過內核線程來進行,會有較大的上下文切換開銷,造成的資源耗費,而 goroutine 是在代碼上直接實現切換,不需要經過內核線程。
goroutine 的優勢:
- 與線程相比, goroutine 非常便宜,可以根據應用程序的需求自動分配, 但在線程的大小通常是固定的
- 使用 goroutine 訪問共享內存的時候 透過?channel?可以避免競態條件的發生
比如,我們計算一個數字的質數,可以寫出如下的代碼:
package main import ( "fmt" "time" ) func main() { num := 300000 start := time.Now() for i := 1; i <= num; i++ { if isPrime(i) { fmt.Println(i) } } end := time.Now() fmt.Println(end.Unix()-start.Unix(), "seconds") } func isPrime(num int) bool { if num == 1 { return false } else if num == 2 { return true } else { for i := 2; i < num; i++ { if num%i == 0 { return false } } return true } }
上面的代碼用?num := 300000?來測試,也就是從?1~300000 之間來看那些數字會是質數,如果是質數的話就把質數輸出,最后看到最終花費了 37 秒。運行結果如下:
使用 goroutine 加快速度
package main import ( "fmt" "time" ) func main() { num := 300000 start := time.Now() for i := 1; i <= num; i++ { go findPrimes(i) } end := time.Now() time.Sleep(5 * time.Second) fmt.Println(end.Unix()-start.Unix(), "seconds") } func findPrimes(num int) { if num == 1 { return } else if num == 2 { fmt.Println(num) } else { for i := 2; i < num; i++ { if num%i == 0 { return } } fmt.Println(num) } }
go findPrimes
?這條語句就可以開啟一個 goroutine,因此以主程序來說這樣等于是開啟 300000 個 goroutine 來各自判斷自己拿到 num 是不是質數這樣。
用?time. Sleep
?來休息五秒來讓 main 主程序不要被關閉,否則由于開啟 goroutine 之后代碼會繼續往下執行,如果沒做 sleep 的話會導致主程序關閉,主程序一關閉 goroutine 就跟著關閉了,我們就看不出效果了。
這邊運行之后會發現輸出的質數出現并不是從小到大的,這是因為這些 goroutine 是一起做事情的,所以誰先做完誰就先輸出這樣。
運行結果如下,最后花費了大概 11 秒:
goroutine 的機制原理
理解 goroutine 機制的原理,關鍵是理解 Go 語言是如何實現調度器模型的。
計算機科學領域的任何問題都可以通過添加間接中間層來解決。GPM 模型就是這一理論的實踐者。
Go 語言中支撐整個調度器實現的主要有 4 個重要結構,分別是 machine(簡稱 M )、goroutine(簡稱 G )、processor(簡稱 P )、Scheduler(簡稱 Sched), 前三個定義在?runtime.h
?中,Sched 定義在?proc.c
?中。
- Sched 結構就是調度器,它維護有存儲 M 和 G 的隊列以及調度器的一些狀態信息等
- M 結構是 Machine,系統線程,它由操作系統管理和調度的,goroutine 就是跑在 M 之上的; M 是一個很大的結構,里面維護小對象內存 cache(mcache)、當前執行的 goroutine、隨機數發生器等等非常多的信息。
- P 結構是 Processor,處理器,它的主要用途就是用來執行 goroutine 的,它維護了一個 goroutine 隊列,即 runqueue。 Processor 是讓我們從 N:1 調度到 M:N 調度的重要部分。
- G 是 goroutine 實現的核心結構,它包含了棧,指令指針,以及其他對調度 goroutine 很重要的信息,例如其阻塞的 channel。
我們分別用三角形,矩形和圓形表示 Machine Processor 和 Goroutine:
在單核處理器的場景下,所有 goroutine 運行在同一個 M 系統線程中,每一個 M 系統線程維護一個 Processor,任何時刻,一個 Processor 中只有一個 goroutine,其他 goroutine 在 runqueue 中等待。
一個 goroutine 運行完自己的時間片后,讓出上下文,回到 runqueue 中。 多核處理器的場景下,為了運行 goroutines,每個 M 系統線程會持有一個 Processor 。
可以看到 Go 的并發用起來非常簡單,用了一個語法糖將內部復雜的實現結結實實的包裝了起來。
其內部可以用下面這張圖來概述:
在單核處理器的場景下,所有 goroutine 運行在同一個 M 系統線程中,每一個 M 系統線程維護一個 Processor,任何時刻,一個 Processor 中只有一個 goroutine,其他 goroutine 在 runqueue 中等待。一個 goroutine 運行完自己的時間片后,讓出上下文,回到 runqueue 中。 多核處理器的場景下,為了運行 goroutines,每個 M 系統線程會持有一個 Processor 。
在正常情況下,scheduler 會按照上面的流程進行調度,但是線程會發生阻塞等情況,看一下goroutine對線程阻塞等的處理。
原文鏈接:https://juejin.cn/post/7140291758201503774
相關推薦
- 2023-05-12 Oracle中實現刪除重復數據只保留一條_oracle
- 2021-11-13 Gateway網關工作原理及使用方法_其它綜合
- 2022-06-24 python學習之讀取配置文件_python
- 2021-12-11 關于docker容器部署redis步驟介紹_docker
- 2022-09-25 Identity Server4/生產模式/證書/certificate/AddSigningCre
- 2022-03-25 Postman如何導出接口的幾種方法(postman怎么把接口導出來)
- 2022-07-15 Android?Camera開發實現可復用的相機組件_Android
- 2022-05-20 使用nginx+tomcat+keepalived實現高可用的詳細步驟_nginx
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支