为什么 channel 上的 go-routine 块被认为是死锁?
Why a go-routine block on channel is considered as deadlock?
根据定义here,死锁与资源争用有关。
在操作系统中,死锁发生在进程或线程进入等待状态时,因为请求的系统资源被另一个等待进程持有,而该等待进程又在等待另一个等待进程持有的资源。如果一个进程不能无限期地改变它的状态,因为它请求的资源正在被另一个等待进程使用,那么系统就被称为死锁。
在下面的代码中:
package main
import "fmt"
func main() {
c := make(chan string)
c <- "John"
fmt.Println("main() stopped")
}
main()
go-routine 阻塞,直到任何其他 go-routine(没有这样的)从该通道读取相同的数据。
但输出显示:
$ bin/cs61a
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/home/user/../myhub/cs61a/Main.go:8 +0x54
$
编辑:
重点:"the main goroutine–which is blocked, hence all goroutines are blocked, hence it's a deadlock."下面的代码中,non-main goroutine也在channel上被阻塞了,不是所有的goroutine都应该被阻塞吗?
package main
import (
"fmt"
"time"
)
func makeRandom(randoms chan int) {
var ch chan int
fmt.Printf("print 1\n")
<-ch
fmt.Printf("print 2\n")
}
func main() {
randoms := make(chan int)
go makeRandom(randoms)
}
编辑 2:
对于您在回答中的观点:"not all your goroutines are blocked so it's not a deadlock"。在下面的代码中,只有 main()
goroutine 被阻塞,而不是 worker()
:
package main
import (
"fmt"
)
func worker() {
fmt.Printf("some work\n")
}
func main() {
ch := make(chan int)
go worker()
<-ch
}
并且输出显示死锁:
$ bin/cs61a
some work
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/home/user/code/src/github.com/myhub/cs61a/Main.go:18 +0x6f
$
理想情况下 main()
不应该退出,因为通道资源被任何一个 go-routine 使用。
为什么通道上的 go-routine 块被认为是死锁?
在 Go 中,死锁是指所有现有的 goroutine 都被阻塞。
您的示例有一个 goroutine(主 goroutine)被阻塞,因此所有 goroutine 都被阻塞,因此这是一个死锁。
注意:由于所有 goroutines 都被阻塞,新的 goroutines 将不会(不能)启动(因为它们只能从 运行 goroutines 启动)。如果所有的 goroutines 都被阻塞了,什么也做不了,那么永远无所事事地等待是没有意义的。所以运行时退出。
编辑:
您在 main 中使用 sleep 的编辑代码与此重复:。基本上睡眠不是永远阻塞操作(睡眠持续时间是有限的),因此在死锁检测中不考虑 goroutine 睡眠。
编辑#2:
从那以后你删除了 sleep()
但它没有改变任何东西。你有 2 个 goroutines:main
和一个执行 makeRandom()
。 makeRandom()
已被阻止,而 main()
未被阻止。所以并不是所有的 goroutine 都被阻塞,所以这不是死锁。
编辑#3:
在您的最后一个示例中,当运行时检测到死锁时,仍然只有一个 goroutine 运行:main()
。的确,您启动了一个执行 worker()
的 goroutine,但这只会打印一个文本并终止。 "Past" goroutines 不算数,终止的 goroutines 也不能做任何改变现有 goroutines 的阻塞状态。只有现有的 goroutines 才算在内。
查看这篇文章以准确理解为什么通道上的 go-routine 块被视为死锁:
http://dmitryvorobev.blogspot.com/2016/08/golang-channels-implementation.html
在你上面的例子中,主 goroutine 被添加到等待队列(sendq)并且在 Go 运行一些从通道接收值的 goroutine 之前不能被释放。
根据定义here,死锁与资源争用有关。
在操作系统中,死锁发生在进程或线程进入等待状态时,因为请求的系统资源被另一个等待进程持有,而该等待进程又在等待另一个等待进程持有的资源。如果一个进程不能无限期地改变它的状态,因为它请求的资源正在被另一个等待进程使用,那么系统就被称为死锁。
在下面的代码中:
package main
import "fmt"
func main() {
c := make(chan string)
c <- "John"
fmt.Println("main() stopped")
}
main()
go-routine 阻塞,直到任何其他 go-routine(没有这样的)从该通道读取相同的数据。
但输出显示:
$ bin/cs61a
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/home/user/../myhub/cs61a/Main.go:8 +0x54
$
编辑:
重点:"the main goroutine–which is blocked, hence all goroutines are blocked, hence it's a deadlock."下面的代码中,non-main goroutine也在channel上被阻塞了,不是所有的goroutine都应该被阻塞吗?
package main
import (
"fmt"
"time"
)
func makeRandom(randoms chan int) {
var ch chan int
fmt.Printf("print 1\n")
<-ch
fmt.Printf("print 2\n")
}
func main() {
randoms := make(chan int)
go makeRandom(randoms)
}
编辑 2:
对于您在回答中的观点:"not all your goroutines are blocked so it's not a deadlock"。在下面的代码中,只有 main()
goroutine 被阻塞,而不是 worker()
:
package main
import (
"fmt"
)
func worker() {
fmt.Printf("some work\n")
}
func main() {
ch := make(chan int)
go worker()
<-ch
}
并且输出显示死锁:
$ bin/cs61a
some work
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
/home/user/code/src/github.com/myhub/cs61a/Main.go:18 +0x6f
$
理想情况下 main()
不应该退出,因为通道资源被任何一个 go-routine 使用。
为什么通道上的 go-routine 块被认为是死锁?
在 Go 中,死锁是指所有现有的 goroutine 都被阻塞。
您的示例有一个 goroutine(主 goroutine)被阻塞,因此所有 goroutine 都被阻塞,因此这是一个死锁。
注意:由于所有 goroutines 都被阻塞,新的 goroutines 将不会(不能)启动(因为它们只能从 运行 goroutines 启动)。如果所有的 goroutines 都被阻塞了,什么也做不了,那么永远无所事事地等待是没有意义的。所以运行时退出。
编辑:
您在 main 中使用 sleep 的编辑代码与此重复:
编辑#2:
从那以后你删除了 sleep()
但它没有改变任何东西。你有 2 个 goroutines:main
和一个执行 makeRandom()
。 makeRandom()
已被阻止,而 main()
未被阻止。所以并不是所有的 goroutine 都被阻塞,所以这不是死锁。
编辑#3:
在您的最后一个示例中,当运行时检测到死锁时,仍然只有一个 goroutine 运行:main()
。的确,您启动了一个执行 worker()
的 goroutine,但这只会打印一个文本并终止。 "Past" goroutines 不算数,终止的 goroutines 也不能做任何改变现有 goroutines 的阻塞状态。只有现有的 goroutines 才算在内。
查看这篇文章以准确理解为什么通道上的 go-routine 块被视为死锁: http://dmitryvorobev.blogspot.com/2016/08/golang-channels-implementation.html
在你上面的例子中,主 goroutine 被添加到等待队列(sendq)并且在 Go 运行一些从通道接收值的 goroutine 之前不能被释放。