Golang Go语言中读写一个nil通道会使协程永久挂起,为什么这样设计呢?

发布于 1周前 作者 nodeper 来自 Go语言

写了一个 demo: https://go.dev/play/p/51-Z_XNr-th

这里写一个 nil 通道会一直阻塞,Go 源码在这一行: https://hub.fastgit.xyz/golang/go/blob/go1.16.10/src/runtime/chan.go#L163

不知道为什么要这样设计?一直阻塞会导致协程泄漏,还不如直接 panic 掉,所以官方为什么这样做呢

.

.

.


Golang Go语言中读写一个nil通道会使协程永久挂起,为什么这样设计呢?

更多关于Golang Go语言中读写一个nil通道会使协程永久挂起,为什么这样设计呢?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

你贴的这个直接运行
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send (nil chan)]:
main.main()
/tmp/sandbox3073028740/prog.go:11 +0x7e

goroutine 5 [chan receive]:
main.main.func1()
/tmp/sandbox3073028740/prog.go:9 +0x4f
created by main.main
/tmp/sandbox3073028740/prog.go:7 +0x6a

Program exited.

更多关于Golang Go语言中读写一个nil通道会使协程永久挂起,为什么这样设计呢?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


https://go.dev/ref/spec#Channel_types
A nil channel is never ready for communication.

> It’s for consistency with select. The semantics of a nil channel are
the same regardless of how it is used. It’s useful that it blocks in a
select, so that’s what it does outside a select.

> If it panicked outside a select, not only would it be inconsistent but
the channel code would need to behave differently in the two cases, a
needless complexity.

https://groups.google.com/g/golang-nuts/c/QltQ0nd9HvE/m/VvDhLO07Oq4J

为了灵活性
go 中,可以对 nil 值 channel 进行读写操作,当对值为 nil 的 channel 进行读取操作时会阻塞,但是对值为 nil 值的 channel 调用 close()会 panic 。使用 nil 值 channel 可以帮助在 select 中禁用某个 select 分支,因为阻塞了所以都不会进入分支语句。

下面是 client-go 中对 nil 值 channel 和单向 channel 的使用的函数代码:

func (p *processorListener) pop() {
defer utilruntime.HandleCrash()
defer close(p.nextCh) // Tell .run() to stop

var nextCh chan<- interface{}
var notification interface{}
for {
select {
case nextCh <- notification:
// Notification dispatched
var ok bool
notification, ok = p.pendingNotifications.ReadOne()
if !ok { // Nothing to pop
nextCh = nil // Disable this select case
}
case notificationToAdd, ok := <-p.addCh:
if !ok {
return
}
if notification == nil { // No notification to pop (and pendingNotifications is empty)
// Optimize the case - skip adding to pendingNotifications
notification = notificationToAdd
nextCh = p.nextCh
} else { // There is already a notification waiting to be dispatched
p.pendingNotifications.WriteOne(notificationToAdd)
}
}
}
}

在Golang(Go语言)中,通道(channel)是用于在多个goroutine(协程)之间进行通信的一种重要机制。关于读写一个nil通道会使协程永久挂起的设计,主要有以下几点原因:

  1. 明确的通信模式:Go语言强调通过通道进行显式通信来共享内存。如果允许对nil通道进行操作,可能会导致难以追踪的并发错误,因为开发者可能期望通道能够正常工作,而实际上通道并未被正确初始化。

  2. 避免潜在的死锁:如果允许对nil通道进行读写,且没有适当的错误处理机制,那么在某些情况下可能会导致死锁。因为协程会无限期地等待一个永远不会被发送或接收数据的通道。

  3. 鼓励安全的并发编程:强制要求在使用通道之前进行初始化,可以促使开发者更加谨慎地处理并发编程中的资源分配和同步问题。这有助于减少并发编程中常见的错误和漏洞。

  4. 一致性:在Go语言中,许多其他操作(如数组访问、映射查找等)在尝试访问无效或未初始化的资源时也会引发运行时错误或异常。对nil通道的操作采取类似的策略,可以保持语言行为的一致性。

综上所述,Go语言设计者对nil通道的操作采取了严格的限制,以防止潜在的并发错误和死锁问题。这种设计虽然可能增加了一些初始化的负担,但长远来看有助于提高程序的稳定性和可维护性。

回到顶部