文章目录


close 解除读取 chan 阻塞

上方的例子是通过 waitgroup 等待让所有的 goroutine 都运行完毕之后,wait 函数才会被放行(注意加上超时处理)。试想一下存在这样的琴况,如果 wait 函数要等待所有 goroutine 结束才能放行,可是这些 gouroutine 因为从 chan 读取不到数据而造成阻塞,则这些 goroutine 可能一直陷入阻塞,而 wait 函数也一直无法放行,就有可能陷入死锁,所以我们有另一种思考,如果不是 wait 函数固执的希望所有 goroutine 必须都要等待他们自己处理完 wait 函数才能继续执行呢?我们假如把 wait 函数考虑换成一种方式可以让 chan 虽然读取不到数据但是依然可以解除 chan 阻塞让 goroutine 运行完成呢?我们也可以看看下面伪代码

// 一个 chan
c := make(chan int)

// 开启多协程,匿名函数中会从 chan 读数据 2 次
go func() {
<- c
<- c
}()

// 上传 1 次数据到 chan
c <- 1

// 旁路协程此时陷入阻塞,因为等待继续读出数据。此时休眠 10s
time.Sleep(10 * time.Second)

// 休眠 10s 发现 chan 依然阻塞着,旁路协程退出不了,此时我们如果采用 waitgroup 的思想使用一个 wait 函数则会造成死锁,因为旁路协程目前 chan 被阻塞,wait 函数又要等旁路协程运行完才能走...此时如果我们直接 close 掉 chan,那么 chan 就不允许再被写入了,但是会直接放开读,读取的数据是 0,这样一旦 close,旁路协程就会解除 chan 的阻塞,旁路协程就被盘“活”了起来
close(c)

// 这里是为了防止 close 掉 chan 之后,chan 还没读出 0,主协程就直接结束了造成 goroutine 泄漏,所以加了一个延时,让 close 完之后,旁路协程也可以解除阻塞运行完毕,最后 1s 时间过去主协程结束
time.Sleep(time.Second)

深入思考

可以类比和 waitgroup 一起看,waitgroup 的 wait 函数是非常执拗的非要等待其他 goroutine 一定要自己执行完程序,wait 函数才会放行,是不是要放行 wait 决定权在所有 goroutine 自己手里,而 close 关闭 chan 则是 close 的调用者自己来决定什么时候需要让其他协程执行完程序(这些协程有可能执行完了,有可能还没有执行完,比如陷入阻塞)

如果旁路协程等待读取 chan 中数据,可是主协程执行到后面发现不会再对 chan 进行写的操作,那么如果不做一些处理直接结束主协程,这些旁路协程就无法正常关闭造成协程泄漏,如果此时我用了一个 close 来“激活”旁路协程,close 作用是关闭 chan,禁止写但是依然可以读,close 掉之后读出的是 0(对于 int 来讲),这样一 close 旁路协程的 chan 就不再被阻塞,可以直接读出数据,这样旁路协程就可以正常关闭

建议扩充

上面代码很精简,作为一个 demo,实际中可能很多地方做封装,比如说要等待多长时间才会去掉 close,比如说 close 掉之后非要延时吗有没有其他机制可以让其他协程先运行完(可以考虑多加一个新的 chan,当这个新 chan 其他协程中最后一步被写入,再把 close 封装成一个函数,函数中 close 后等待新 chan 读出数据,这样就可以做到 close 的完全结束依赖于其他协程的结束),还比如说其中可以做很多超时处理的操作