现在只有主 goroutine 存在。
接收不发送
发送不接收会导致发送者阻塞,反之,接收不发送也会导致接收者阻塞。直接看示例代码,如下:
package main
func main() {
defer func() {
time.Sleep(time.Second)
fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
}()
var ch chan struct{}
go func() {
ch <- struct{}{}
}()
}
运行结果显示:
the number of goroutines: 2
当然,我们正常不会遇到这么傻的情况发生,现实工作中的案例更多可能是发送已完成,但是发送者并没有关闭 channel,接收者自然也无法知道发送完毕,阻塞因此就发生了。
解决方案是什么?那当然就是,发送完成后一定要记得关闭 channel。
nil channel
向 nil channel 发送和接收数据都将会导致阻塞。这种情况可能在我们定义 channel 时忘记初始化的时候发生。
示例代码:
func main() {
defer func() {
time.Sleep(time.Second)
fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
}()
var ch chan int
go func() {
<-ch
// ch<-
}()
}
两种写法:<-ch 和 ch<- 1,分别表示接收与发送,都将会导致阻塞。如果想实现阻塞,通过 nil channel 和 done channel 结合实现阻止 main 函数的退出,这或许是可以一试的方法。
func main() {
defer func() {
time.Sleep(time.Second)
fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
}()
done := make(chan struct{})
var ch chan int
go func() {
defer close(done)
}()
select {
case <-ch:
case <-done:
return
}
}
在 goroutine 执行完成,检测到 done 关闭,main 函数退出。
真实的场景
真实的场景肯定不会像案例中的简单,可能涉及多阶段 goroutine 之间的协作,某个 goroutine 可能即使接收者又是发送者。但归根到底,无论什么使用模式。都是把基础知识组织在一起的合理运用。
传统同步机制
虽然,一般推荐 Go 并发数据的传递,但有些场景下,显然还是使用传统同步机制更合适。Go 中提供传统同步机制主要在 sync 和 atomic 两个包。接下来,我主要介绍的是锁和 WaitGroup 可能导致 goroutine 的泄露。
Mutex
和其他语言类似,Go 中存在两种锁,排它锁和共享锁,关于它们的使用就不作介绍了。我们以排它锁为例进行分析。
示例如下:
func main() {
total := 0
defer func() {
time.Sleep(time.Second)
fmt.Println("total: ", total)
fmt.Println("the number of goroutines: ", runtime.NumGoroutine())
}()
var mutex sync.Mutex
for i := 0; i < 2; i++ {
go func() {
mutex.Lock()
total += 1
}()
}
}









