[英]Why is this Go code blocking?
我寫了以下程序:
package main
import (
"fmt"
)
func processevents(list chan func()) {
for {
//a := <-list
//a()
}
}
func test() {
fmt.Println("Ho!")
}
func main() {
eventlist := make(chan func(), 100)
go processevents(eventlist)
for {
eventlist <- test
fmt.Println("Hey!")
}
}
由於頻道事件列表是一個緩沖頻道,我想我應該得到輸出“嘿!”的100倍,但它只顯示一次。 我的錯誤在哪里?
從Go 1.2開始,調度程序的工作原理是先發制人的多任務處理 。 這意味着原始問題(以及下面介紹的解決方案)中的問題不再相關。
調度程序中的搶占
在以前的版本中,永遠循環的goroutine可能會在同一個線程上餓死其他goroutine,這是GOMAXPROCS只提供一個用戶線程時的一個嚴重問題。 在Go> 1.2中,部分解決了這個問題:在進入函數時偶爾會調用調度程序。 這意味着任何包含(非內聯)函數調用的循環都可以被搶占,允許其他goroutine在同一個線程上運行。
它不會阻止寫入。 它陷入了無限循環的processevents
。 這個循環永遠不會產生調度程序,導致所有goroutine無限期地鎖定。
如果您注釋掉對processevents
的調用,您將獲得預期的結果,直到第100次寫入。 程序在此時會感到恐慌,因為沒有人從頻道中讀取內容。
另一個解決方案是在循環中調用runtime.Gosched()
。
使用Go1.0.2,Go的調度程序基於協作式多任務處理原理。 這意味着它通過讓這些例程在某些條件下與調度程序交互,將CPU時間分配給在給定OS線程內運行的各種goroutine。 當在goroutine中執行某些類型的代碼時,會發生這些“交互”。 在go的情況下,這涉及進行某種I / O,系統調用或內存分配(在某些條件下)。
在空循環的情況下,不會遇到這樣的情況。 因此,只要該循環正在運行,就不允許調度程序運行其調度算法。 這因此可以防止它將CPU時間分配給等待運行的其他goroutine,並且您觀察到的結果隨之發生:您有效地創建了一個無法被調度程序檢測到或中斷的死鎖。
在Go中通常不需要空循環,並且在大多數情況下,將指示程序中的錯誤。 如果由於某種原因確實需要它,則必須通過在每次迭代中調用runtime.Gosched()
來手動屈服於調度程序。
for {
runtime.Gosched()
}
將GOMAXPROCS
設置為值> 1
被提及作為解決方案。 雖然這將解決您觀察到的直接問題,但如果調度程序決定將循環goroutine移動到其自己的OS線程,它將有效地將問題移動到不同的OS線程。 除非您在processevents
函數的開頭調用runtime.LockOSThread()
,否則無法保證這一點。 即便如此,我仍然不會依賴這種方法來成為一個好的解決方案。 只需在循環本身中調用runtime.Gosched()
,就可以解決所有問題,無論goroutine運行在哪個OS線程中。
這是另一種解決方案 - 使用range
從通道讀取。 此代碼將正確地生成調度程序,並在通道關閉時正確終止。
func processevents(list chan func()) {
for a := range list{
a()
}
}
好消息,自Go 1.2(2013年12月)以來,原始程序現在按預期工作。 你可以在Playground試試吧 。
這在Go 1.2發行說明的 “調度程序中的搶占”部分中進行了解釋:
在以前的版本中,永遠循環的goroutine可能會在同一個線程上餓死其他goroutine,這是GOMAXPROCS只提供一個用戶線程時的一個嚴重問題。 在Go 1.2中,部分解決了這個問題:在進入函數時偶爾會調用調度程序。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.