淺談golang for 循環(huán)中使用協(xié)程的問(wèn)題
兩個(gè)例子
package main import ( "fmt" "time" ) func Process1(tasks []string) { for _, task := range tasks { // 啟動(dòng)協(xié)程并發(fā)處理任務(wù) go func() { fmt.Printf("Worker start process task: %s\n", task) }() } } func main() { tasks := []string{"1", "2", "3", "4", "5"} Process1(tasks) time.Sleep(2 * time.Second) }
結(jié)果:
第一次運(yùn)行
Worker start process task: 3 Worker start process task: 4 Worker start process task: 4 Worker start process task: 5 Worker start process task: 5
第二次運(yùn)行
Worker start process task: 2 Worker start process task: 5 Worker start process task: 5 Worker start process task: 5 Worker start process task: 5
package main import ( "fmt" "time" ) func Process1(tasks []string) { for _, task := range tasks { // 啟動(dòng)協(xié)程并發(fā)處理任務(wù) go func() { fmt.Printf("Worker start process task: %s\n", task) }() } } func Process2(tasks []string) { for _, task := range tasks { // 啟動(dòng)協(xié)程并發(fā)處理任務(wù) go func(t string) { fmt.Printf("Worker start process task: %s\n", t) }(task) } } func main() { tasks := []string{"1", "2", "3", "4", "5"} Process2(tasks) time.Sleep(2 * time.Second) }
結(jié)果
第一次運(yùn)行
Worker start process task: 5 Worker start process task: 4 Worker start process task: 2 Worker start process task: 3 Worker start process task: 1
第二次運(yùn)行
Worker start process task: 2 Worker start process task: 5 Worker start process task: 4 Worker start process task: 1 Worker start process task: 3
上述問(wèn)題,有個(gè)共同點(diǎn)就是都引用了循環(huán)變量。即在for index, value := range xxx語(yǔ)句中,
index和value便是循環(huán)變量。不同點(diǎn)是循環(huán)變量的使用方式,有的是直接在協(xié)程中引用(題目一),有的作為參數(shù)傳遞(題目二)。
循環(huán)變量是易變的
首先,循環(huán)變量實(shí)際上只是一個(gè)普通的變量。
語(yǔ)句for index, value := range xxx中,每次循環(huán)index和value都會(huì)被重新賦值(并非生成新的變量)。
如果循環(huán)體中會(huì)啟動(dòng)協(xié)程(并且協(xié)程會(huì)使用循環(huán)變量),就需要格外注意了,因?yàn)楹芸赡苎h(huán)結(jié)束后協(xié)程才開(kāi)始執(zhí)行,
此時(shí),所有協(xié)程使用的循環(huán)變量有可能已被改寫。(是否會(huì)改寫取決于引用循環(huán)變量的方式)
循環(huán)變量需要綁定
在題目一中,協(xié)程函數(shù)體中引用了循環(huán)變量task,協(xié)程從被創(chuàng)建到被調(diào)度執(zhí)行期間循環(huán)變量極有可能被改寫,所以會(huì)出現(xiàn)兩次結(jié)果相差較大,比如第一個(gè)協(xié)程啟動(dòng)for range變量正好循環(huán)到3,for屬于主協(xié)程的一部分。go func是子協(xié)程,主子分開(kāi)看。這種情況下,其實(shí)for range里面的循環(huán)變量沒(méi)有跟子協(xié)程綁定,稱之為變量沒(méi)有綁定。所以,題目一打印結(jié)果是混亂的。很有可能(隨機(jī))所有協(xié)程執(zhí)行的task都是列表中的最后一個(gè)task,也可能不是。
在題目二中,協(xié)程函數(shù)體中并沒(méi)有直接引用循環(huán)變量task,而是使用的參數(shù)與協(xié)程進(jìn)行了綁定。而在創(chuàng)建協(xié)程時(shí),循環(huán)變量task
作為函數(shù)參數(shù)傳遞給了協(xié)程。參數(shù)傳遞的過(guò)程實(shí)際上也生成了新的變量,也即間接完成了綁定。
所以,題目二實(shí)際上是沒(méi)有問(wèn)題的。就是實(shí)際參數(shù)順序是按照f(shuō)or range產(chǎn)生的變量順序綁定給子協(xié)程的。
ps:
簡(jiǎn)單點(diǎn)來(lái)說(shuō)
如果循環(huán)體沒(méi)有并發(fā)出現(xiàn),則引用循環(huán)變量一般不會(huì)出現(xiàn)問(wèn)題;
如果循環(huán)體有并發(fā),則根據(jù)引用循環(huán)變量的位置不同而有所區(qū)別
通過(guò)參數(shù)完成綁定,則一般沒(méi)有問(wèn)題;
函數(shù)體中引用,則需要顯式地綁定
補(bǔ)充:Go語(yǔ)言的協(xié)程中,寫死循環(huán)的注意點(diǎn):
現(xiàn)象:
在寫Go的多協(xié)程程序時(shí),出現(xiàn)過(guò)幾次無(wú)法理解的情況。
有一次,我想寫一個(gè)能跑滿cpu的程序,最容易想到的就是,開(kāi)幾個(gè)Go的協(xié)程,每個(gè)協(xié)程里寫死循環(huán)。沒(méi)想到,運(yùn)行的時(shí)候發(fā)現(xiàn),協(xié)程就只開(kāi)出了一個(gè)。
另一次,我寫了個(gè)程序,也是開(kāi)了多個(gè)協(xié)程。因?yàn)槿绻蛔枞≈骱瘮?shù),主函數(shù)一結(jié)束,程序就會(huì)結(jié)束。所以我就在主函數(shù)結(jié)束前加了個(gè)死循環(huán)。然后就發(fā)現(xiàn)整個(gè)協(xié)程都被卡住了。
分析:
其實(shí),這個(gè)東西是協(xié)程的特點(diǎn)。以前沒(méi)用過(guò)協(xié)程,加上Go又說(shuō)可以當(dāng)線程用。所以想當(dāng)然的寫了死循環(huán)。
準(zhǔn)確的說(shuō),是在Go語(yǔ)言里,寫了死循環(huán),并且死循環(huán)內(nèi)并沒(méi)有什么系統(tǒng)調(diào)用,只有簡(jiǎn)單的計(jì)算這類的。你就會(huì)發(fā)現(xiàn),Go的協(xié)程調(diào)度就廢掉了。
協(xié)程并非像線程那樣,是由CPU中斷來(lái)觸發(fā)切換的。它不是應(yīng)用程序能控制的(操作系統(tǒng)內(nèi)核的某些關(guān)鍵操作會(huì)被保護(hù),不被中斷)。即使你在線程里寫了死循環(huán),只要周期一到,CPU產(chǎn)生終端,死循環(huán)會(huì)被打斷,重新調(diào)度。但是,協(xié)程就不是這樣了,協(xié)程的調(diào)度其實(shí)是在協(xié)程調(diào)用了某個(gè)系統(tǒng)調(diào)用時(shí),自動(dòng)跳到另一個(gè)協(xié)程執(zhí)行。也就是這個(gè)“中斷”是程序主動(dòng)產(chǎn)生的,而不是被”中斷”。
所以,協(xié)程中,如果你寫了死循環(huán),那你的死循環(huán)就會(huì)一直跑著,而不會(huì)讓別的協(xié)程運(yùn)行。主函數(shù)中也是一樣,而且主函數(shù)中執(zhí)行這個(gè)會(huì)讓整個(gè)協(xié)程卡住,因?yàn)檎{(diào)度的代碼沒(méi)法被執(zhí)行。
在Go語(yǔ)言中,如果你想寫死循環(huán),循環(huán)里面沒(méi)有系統(tǒng)調(diào)用,又想讓Go的協(xié)程能起作用,只需要在死循環(huán)里面加一條語(yǔ)句即可。估計(jì)系統(tǒng)調(diào)用時(shí)也是這個(gè)語(yǔ)句起的作用。
runtime.Gosched() //主動(dòng)讓出時(shí)間片
還可以使用
select{}
來(lái)實(shí)現(xiàn)無(wú)限阻塞,而不是使用for{}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
- Golang的循環(huán)語(yǔ)句和循環(huán)控制語(yǔ)句詳解
- golang 跳出多重循環(huán)的高級(jí)break用法說(shuō)明
- golang數(shù)據(jù)結(jié)構(gòu)之golang稀疏數(shù)組sparsearray詳解
- Golang中數(shù)據(jù)結(jié)構(gòu)Queue的實(shí)現(xiàn)方法詳解
- 詳解golang避免循環(huán)import問(wèn)題(“import cycle not allowed”)
- golang中for循環(huán)遍歷channel時(shí)需要注意的問(wèn)題詳解
- Golang迭代如何在Go中循環(huán)數(shù)據(jù)結(jié)構(gòu)使用詳解
相關(guān)文章
教你利用Golang可選參數(shù)實(shí)現(xiàn)可選模式
本文討論Golang函數(shù)可選參數(shù)及函數(shù)類型,以及如何利用可選函數(shù)類型實(shí)現(xiàn)可選模式。同時(shí)通過(guò)構(gòu)造函數(shù)作為示例,實(shí)現(xiàn)強(qiáng)大帶可選參數(shù)的構(gòu)造函數(shù),讓代碼更直觀、靈活、支持?jǐn)U展2023-01-01如何使用Go語(yǔ)言獲取當(dāng)天、昨天、明天、某天0點(diǎn)時(shí)間戳以及格式化時(shí)間
這篇文章主要給大家介紹了關(guān)于如何使用Go語(yǔ)言獲取當(dāng)天、昨天、明天、某天0點(diǎn)時(shí)間戳以及格式化時(shí)間的相關(guān)資料,格式化時(shí)間戳是將時(shí)間戳轉(zhuǎn)換為特定的日期和時(shí)間格式,文中通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10Golang實(shí)現(xiàn)文件夾的創(chuàng)建與刪除的方法詳解
這篇文章主要介紹了如何利用Go語(yǔ)言實(shí)現(xiàn)對(duì)文件夾的常用操作:創(chuàng)建于刪除。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-05-05Golang觀察者模式優(yōu)化訂單處理系統(tǒng)實(shí)例探究
當(dāng)涉及到訂單處理系統(tǒng)時(shí),觀察者設(shè)計(jì)模式可以用于實(shí)現(xiàn)訂單狀態(tài)的變化和通知,在這篇文章中,我們將介紹如何使用Golang來(lái)實(shí)現(xiàn)觀察者設(shè)計(jì)模式,并提供一個(gè)基于訂單處理系統(tǒng)的代碼示例2024-01-01Go標(biāo)準(zhǔn)庫(kù)strconv實(shí)現(xiàn)string類型與其他基本數(shù)據(jù)類型之間轉(zhuǎn)換
這篇文章主要為大家介紹了Go標(biāo)準(zhǔn)庫(kù)strconv實(shí)現(xiàn)string類型與其他基本數(shù)據(jù)類型之間轉(zhuǎn)換示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11