網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
Go 協(xié)程和普通線程對(duì)比
Go 擁有極強(qiáng)的并發(fā)編程能力,而 Go 并發(fā)編程強(qiáng)勢(shì)原因,一部分原因是因?yàn)檎Z(yǔ)法簡(jiǎn)單 ,還有一個(gè)更核心的原因是 Go 中協(xié)程 goroutine (用戶態(tài)線程)的存在。
在 Go 中 goroutine 是比普通線程(內(nèi)核態(tài)線程)更加輕量化的存在。
內(nèi)核級(jí)線程(線程)
內(nèi)核態(tài)線程簡(jiǎn)稱線程,是在內(nèi)核中維護(hù)了線程表進(jìn)行跟蹤監(jiān)控的線程。是 CPU 調(diào)度和分派的基本單位,是具備進(jìn)程某些屬性,能夠獨(dú)立運(yùn)行的更小單位,所以也稱為輕量級(jí)進(jìn)程。
如果使用內(nèi)核態(tài)線程在一個(gè) CPU 上實(shí)現(xiàn)任務(wù)的并發(fā),那么 CPU 會(huì)通過(guò)分配時(shí)間片的方式去執(zhí)行任務(wù),當(dāng)正在運(yùn)行的任務(wù)所分配的時(shí)間片用完了,就會(huì)切換到另一個(gè)任務(wù),而在切換的之前會(huì)保存當(dāng)前任務(wù)的狀態(tài)(CPU寄存器、程序計(jì)數(shù)器中的內(nèi)容),當(dāng)下次再次切換到這個(gè)任務(wù)之前就會(huì)先加載之前保存到任務(wù)狀態(tài),這個(gè)過(guò)程稱作上下文切換。
而在上下文切換的過(guò)程該線程會(huì)從用戶態(tài)轉(zhuǎn)為內(nèi)核態(tài),加載時(shí)再由內(nèi)核態(tài)轉(zhuǎn)為用戶態(tài)。而用戶態(tài)和內(nèi)核態(tài)切換的代價(jià)是很高的,從用戶態(tài)到內(nèi)核態(tài)的切換比較耗費(fèi)資源的,而且內(nèi)核資源是很珍貴的,所以內(nèi)核態(tài)線程過(guò)多時(shí),可能會(huì)出現(xiàn)內(nèi)核資源耗盡的情況。
線程優(yōu)點(diǎn)
- 多核 CPU 利用 :內(nèi)核具有0級(jí)權(quán)限,因此可以在多個(gè) CPU 上執(zhí)行內(nèi)核線程
- 操作系統(tǒng)級(jí)優(yōu)化:線程之間是相對(duì)獨(dú)立的,一個(gè)線程阻塞不會(huì)影響其他線程的執(zhí)行。內(nèi)核態(tài)線程在進(jìn)行你 IO 操作時(shí)不需要進(jìn)行系統(tǒng)調(diào)用 。
線程缺點(diǎn)
- 創(chuàng)建的時(shí)候需要切換到內(nèi)核態(tài),所以創(chuàng)建成本比較高。
- 切換的時(shí)候,需要用戶態(tài)和內(nèi)核態(tài)之間頻繁的切換,切換成本較高。
- 線程數(shù)量有限制,當(dāng)內(nèi)核態(tài)線程過(guò)多時(shí),會(huì)出現(xiàn)內(nèi)核資源耗盡的情況。
用戶級(jí)線程(協(xié)程)
用戶態(tài)線程簡(jiǎn)稱協(xié)程,是完全由用戶控制的線程,內(nèi)核并不會(huì)感知到它的存在,用戶級(jí)線程的創(chuàng)建、銷毀、調(diào)度、狀態(tài)變更以及其中的代碼和數(shù)據(jù)都完全需要我們的程序自己去實(shí)現(xiàn)和處理。因?yàn)槭谴嬖谠谟脩艨臻g上的線程,所以在切換時(shí)不存在用戶態(tài)和內(nèi)核態(tài)的轉(zhuǎn)換。
協(xié)程優(yōu)點(diǎn)
- 不由內(nèi)核管理,由用戶自主管理,創(chuàng)建成本比較低。
- 不存在內(nèi)核態(tài)和用戶態(tài)的切換,切換成本比較低。
- 不需要操作系統(tǒng)去調(diào)度,所以可以跨操作系統(tǒng),并且更加靈活、更容易控制。
協(xié)程缺點(diǎn)
- 因?yàn)椴僮飨到y(tǒng)的內(nèi)核看不到協(xié)程,所以同屬于一個(gè)進(jìn)程的協(xié)程只能占有一個(gè)核,不能發(fā)揮多核優(yōu)勢(shì)。
- 操作系統(tǒng)不能主動(dòng)調(diào)度協(xié)程,所以后面的協(xié)程只能等待前面的寫成執(zhí)行完才能獲取到 CPU 資源。
- 同上,當(dāng)前協(xié)程阻塞會(huì)導(dǎo)致后面的寫成無(wú)法執(zhí)行。內(nèi)核協(xié)作成本高,當(dāng)要進(jìn)行一些高權(quán)限的操作,比如讀寫文件時(shí),需要頻繁地進(jìn)行用戶態(tài)和內(nèi)核態(tài)的切換。
調(diào)度器(GPM)
GPM 是 Go 語(yǔ)言運(yùn)行時(shí)系統(tǒng)的重要組成部分
其中 G 代表 goroutine 即協(xié)程,M 代表 machine 即系統(tǒng)線程、P 代表 Proccessor 代表寫成和線程之間的聯(lián)系
簡(jiǎn)單點(diǎn)理解就是:
G 是要被執(zhí)行的任務(wù)實(shí)例
M 是實(shí)際的執(zhí)行載體,需要綁定 P 成為一個(gè)執(zhí)行單元才能調(diào)度 G
P 可以看作是任務(wù)處理器,P 中保存 M 執(zhí)行 G 時(shí)的一些資源,P 決定了哪個(gè) G 能配分配到哪個(gè) M 上
G\P\M 之間的工作關(guān)系
G 要調(diào)度到 M 上才能運(yùn)行,M 需要關(guān)聯(lián) P 才可以執(zhí)行 Go 代碼,但當(dāng)處理阻塞或系統(tǒng)調(diào)用中時(shí),M 可以不用關(guān)聯(lián) P
當(dāng)進(jìn)程啟動(dòng)時(shí),會(huì)創(chuàng)建若干個(gè)(一般是內(nèi)核數(shù)量)系統(tǒng)線程 M 和任務(wù)處理器 P,并一一綁定,因?yàn)橐粋€(gè)線程對(duì)應(yīng)一個(gè) CPU 就不會(huì)出現(xiàn)上下文的切切換,能更大限度的節(jié)省資源。同時(shí)每一個(gè)任務(wù)處理器 P 都會(huì)對(duì)應(yīng)一個(gè)私有的任務(wù)隊(duì)列,但是私有隊(duì)列有上限的,所以所有任務(wù)處理器還會(huì)有一個(gè)公用的全局隊(duì)列。
當(dāng)有一個(gè) G 被創(chuàng)建時(shí),就會(huì)被放到一個(gè)任務(wù)處理器 P 的私有隊(duì)列中等待被調(diào)度。如果所有的私有隊(duì)列都滿了,就會(huì)被放到全局隊(duì)列中去。當(dāng) P 尋找 G 的時(shí)候,會(huì)先從自己的私有隊(duì)列中尋找,如果沒(méi)找到,再去全局隊(duì)列中尋找,如果還是沒(méi)有,它會(huì)去搶別的 P 中的 G。當(dāng)任何地方都不能找到需要調(diào)度的 G 時(shí),M 和 P 則會(huì)斷開(kāi)連接。
當(dāng) G 中進(jìn)行了系統(tǒng)調(diào)用,則 M 也會(huì)進(jìn)入系統(tǒng)調(diào)用狀態(tài),此時(shí) P 會(huì)去尋找其他未在工作的 M 并為其尋找需要調(diào)度的任務(wù)。當(dāng) G 完成了系統(tǒng)調(diào)用,會(huì)進(jìn)入空閑的私有隊(duì)列或者全局隊(duì)列,然后等待再次被調(diào)度。
所以,G、M 之間沒(méi)有直接的聯(lián)系,一個(gè) G 可能被不同的 M 調(diào)度,一個(gè) M 也可以調(diào)度不同的 G。
Go 使用協(xié)程
創(chuàng)建協(xié)程
通過(guò)關(guān)鍵字 go 創(chuàng)建一個(gè)協(xié)程
一:普通函數(shù)創(chuàng)建
func goroutineTest(i int) { fmt.Println(i) } func main() { for i := 0; i < 10; i++ { go goroutineTest(i) } time.Sleep(time.Millisecond * 500) }
二:匿名函數(shù)創(chuàng)建
func main() { for i := 0; i < 10; i++ { go func() { fmt.Println(i) }() } time.Sleep(time.Millisecond * 500) }
三:匿名帶參數(shù)函數(shù)創(chuàng)建
func main() { for i := 0; i < 10; i++ { go func(str string) { fmt.Println(str) }(fmt.Sprintf("%s%d", "s", i)) } time.Sleep(time.Millisecond * 500) }
注意
一:主協(xié)程不會(huì)等待子協(xié)程執(zhí)行完畢
以下列代碼為例,一般來(lái)說(shuō)控制臺(tái)不會(huì)打印任何東西。原因是當(dāng)代碼運(yùn)行到 go goroutineTest(i) 這一行時(shí),并不是真正的會(huì)運(yùn)行它,而是將它放到了任務(wù)隊(duì)列中。所以可能子協(xié)程還沒(méi)開(kāi)始執(zhí)行,主協(xié)程就已經(jīng)退出了。所以我們可以再主協(xié)程加上等待。
func goroutineTest(i int) { fmt.Println(i) } func main() { for i := 0; i < 10; i++ { go goroutineTest(i) } time.Sleep(time.Millisecond * 500) }
二:協(xié)程的執(zhí)行沒(méi)有順序
同上,當(dāng)寫成創(chuàng)建是,并不是立即執(zhí)行,而是被分到不同的隊(duì)列中等待被調(diào)度,而調(diào)度是不能保證先后順序的(只能保證同一個(gè)隊(duì)列中的任務(wù)先進(jìn)先出),因此上述代碼打印出來(lái)的結(jié)果大部分情況下時(shí)亂序的。
三:無(wú)參匿名函數(shù)中的變量變化
func main() { for i := 0; i < 3; i++ { go func() { fmt.Println(i) }() } time.Sleep(time.Millisecond * 500) }
這段代碼的打印結(jié)果如下:
2
2
3
因?yàn)槟涿瘮?shù)不攜帶參數(shù)信息,只有匿名方法執(zhí)行到 fmt.Println(i) 這一行時(shí)才會(huì)去讀取 i 的值。又因?yàn)閰f(xié)程執(zhí)行是有延遲的,所以當(dāng)執(zhí)行到這一句時(shí), i 的值可能已經(jīng)發(fā)生了變化。
下一篇再研究主協(xié)程等待子協(xié)程執(zhí)行完畢以及確保子協(xié)程執(zhí)行順序的方法…
原文鏈接:https://blog.csdn.net/qq_40096897/article/details/127536472
相關(guān)推薦
- 2022-04-29 Go語(yǔ)言中的通道channel詳情_(kāi)Golang
- 2023-07-30 使用Elementui元素動(dòng)態(tài)增減表單組件
- 2022-07-10 初中級(jí)前端程序員必用且夠用的git命令同時(shí)推送到github/gitee及三種常用場(chǎng)景
- 2023-04-01 Python使用pptx實(shí)現(xiàn)復(fù)制頁(yè)面到其他PPT中_python
- 2022-07-20 C語(yǔ)言循環(huán)鏈表的原理與使用操作_C 語(yǔ)言
- 2023-01-07 Flutter?Widget開(kāi)發(fā)Shortcuts快捷鍵實(shí)例_Android
- 2022-05-17 Spring Cloud OpenFeign源碼解析
- 2022-07-18 spring boot 中解決 Invalid character found in the req
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支