前言
众所周知,channel在使用之前需要通过make去进行初始化。否则会是一个nil的channel。在阅读源码中碰到了很多对其的处理步骤,所以学习了一下这边。
正常使用会遇到的错误情况
func main() {
var c1 chan int
//c1 := make(chan int, 0)
var wg sync.WaitGroup
wg.Add(2)
go func() {
c1 <- 1
wg.Done()
}()
go func() {
tmp := <-c1
fmt.Println(tmp)
wg.Done()
}()
wg.Wait()
}
这是自己的一段测试代码,运行后会出现——fatal error: all goroutines are asleep - deadlock!
但如果取消注释的那行,即可以正常运行。
源码情况
if c == nil {
if !block {
return false
}
gopark(nil, nil, waitReasonChanSendNilChan, traceBlockForever, 2)
throw("unreachable")
}
可以看到,会进入gopark
函数,其中
// Puts the current goroutine into a waiting state and calls unlockf on the
// system stack.
//
// If unlockf returns false, the goroutine is resumed.
//
// unlockf must not access this G's stack, as it may be moved between
// the call to gopark and the call to unlockf.
//
// Note that because unlockf is called after putting the G into a waiting
// state, the G may have already been readied by the time unlockf is called
// unless there is external synchronization preventing the G from being
// readied. If unlockf returns false, it must guarantee that the G cannot be
// externally readied.
//
// Reason explains why the goroutine has been parked. It is displayed in stack
// traces and heap dumps. Reasons should be unique and descriptive. Do not
// re-use reasons, add new ones.
func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceReason traceBlockReason, traceskip int) {
if reason != waitReasonSleep {
checkTimeouts() // timeouts may expire while two goroutines keep the scheduler busy
}
mp := acquirem()
gp := mp.curg
status := readgstatus(gp)
if status != _Grunning && status != _Gscanrunning {
throw("gopark: bad g status")
}
mp.waitlock = lock
mp.waitunlockf = unlockf
gp.waitreason = reason
mp.waitTraceBlockReason = traceReason
mp.waitTraceSkip = traceskip
releasem(mp)
// can't do anything that might move the G between Ms here.
mcall(park_m)
}
即可以看到,goroutine被休眠了,需要等待唤醒。但是如何唤醒…我的确不知道了。从GPT的解答也没有看出来该怎么做,算是调度器的事了可能。
使用场景
这部分主要是参考GPT给我的答案,但我说实话没太看懂他是做什么的…所以干脆复制到这里,期待评论区大佬补充。
以下内容由GPT 4.0-0125生成
nil通道在Go语言中虽然在大多数情况下会导致阻塞,但它们在特定的场景下可以被用作有用的同步工具或状态控制机制。下面是一些使用nil通道的场景:
- 动态控制select语句的case执行
在select语句中,对nil通道的操作(不论是发送还是接收)都不会被选中执行。这个特性可以用来动态地启用或禁用select语句的某些分支。通过将通道设置为nil或一个实际的通道,你可以控制哪些操作可以执行,这样可以基于程序的当前状态动态地改变程序的行为。
var c chan int // c是一个nil通道
go func() {
select {
case c <- 1:
// 这个case不会执行,因为c是nil
default:
fmt.Println("c is nil")
}
}()
// 根据条件动态地使某个case可执行或不可执行
if condition {
c = make(chan int, 1) // 初始化c,使得上面的case可执行
}
-
防止goroutine无谓的运行
有时候你可能希望基于某些条件阻止goroutine的进一步执行,直到这个条件改变。使用nil通道可以达到这个目的,因为从nil通道的接收或向nil通道的发送都会阻塞,你可以利用这一点来控制goroutine的执行流程。 -
资源清理和管理
在一些需要根据资源是否可用来控制并发操作的场景中,nil通道可以作为一种表示资源状态的机制。比如,当某个资源不可用时,相关的通道可以被设置为nil,相应的操作因为等待在nil通道上而阻塞,直到资源再次变得可用时,通道被重新初始化,阻塞的操作得以继续。 -
简化代码逻辑
在某些情况下,使用nil通道可以简化代码逻辑。例如,在使用select处理多个通道时,如果某个通道暂时不需要被处理,将其设置为nil可以避免引入额外的逻辑来判断是否需要处理该通道。
总结
虽然nil通道的使用场景比较特殊,但它们在控制并发程序的行为、管理资源状态等方面提供了一种简洁而强大的机制。正确使用nil通道可以使得并发程序的设计更加灵活和清晰。
总结
我的确不太明白这个东西有啥用,但给我的警示就是:一定记得初始化。