golang-design / under-the-hood

📚 Go: Under The Hood | Go 语言原本 | https://golang.design/under-the-hood

Home Page:https://golang.design/under-the-hood

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ch11type/chan: selectnbsend 函数非阻塞操作

cnbailian opened this issue · comments

动机

我在文章的上半部分发现了 Go 对于 select 非阻塞 channel 操作有这样一个特性:可以使用 select 可以向 nil channel 发送消息而不会死锁

需求说明

相关源码:

func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
	if c == nil {
		if !block {
			return false
		}
		gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)
		throw("unreachable")
	}
        (......)
}

我暂时也想不出来哪种场景会出现 select 向 nil channel 发送消息的场景,但是 Go 设计了这个特性,我觉得可以在文章中讲 block 值为 false 时提一下。

其他

在 select 只有一个 case 时,会检测出这种情况,并且当作 no cases 直接死掉了

func main() {
	var ch chan int
	select {
	case ch <- 1:
		(......)
	}
}
// fatal error: all goroutines are asleep - deadlock!
// goroutine 1 [select (no cases)]:

如果有 default,或其他正常 case,就可以使用这个特性,不太清楚 select 在哪里做了处理。

你说的这种情况确实存在,而且仅在只有两个 case 且其中一个 case 是 default 时才能使用。

这种情况是一种编译器的优化,并在在 walkselectcases 这部分进一部分简单提及的。不过这段内容比较靠后:

https://github.com/changkun/go-under-the-hood/blob/master/book/zh-cn/part3compile/ch11type/chan.md#L1079

似乎与源码中发生的行为并不一致,因为按照调用,当锁被解除后,并没有任何 panic。
这是为什么呢?事实上,通过对程序进行反编译,我们能够观察到,**当 select 语句只有一个 case 时,`select` 关键字
是没有被翻译成 `selectgo` 的。因为只有一个 case 的 `select` 与 `if` 是没有区别的,这也是编译器本身对代码的一个优化,
消除了这种情况下调用 `selectgo` 的性能开销:

我考虑优化一下这部分的内容描述。

明白了,虽然没看懂代码,但注释看懂了...

// if ch == nil { block() }; n;

当只有一个 case 并且 channel 值为 nil 的情况下,直接编译为 block 函数调用,也就是处理掉这种情况,直接作为 no case 处理。
thanks