技巧1:关闭某个chan时,所有阻塞读取该chan的协程都会收到通知
举例:如果协程A希望协程B在处理完某个事情后自己能收到通知,可以向协程B的函数参数中传入一个chan,然后协程A阻塞读取该chan。协程B在需要通知其的时候关闭chan即可,这样协程A就会返回。
常用于:某个协程内新建了一个子协程,但是希望子协程在某个时候,例如初始化完成时通知自己
注意事项:是直接关闭chan就可以了,不需要向这个协程内压入数据,因为压入数据的话最终还得关闭chan
代码实验:
1. 主协程创建一个子协程,在子协程的函数参数中传入一个无缓冲的chan
2. 主协程阻塞读取这个chan
3. 子协程在2秒后关闭这个chan,可以发现主协程从阻塞中返回
实验结果如下图(可以发现子协程一旦关闭chan,主协程立马就从阻塞中返回了)
完整代码:
package main
import (
"fmt"
"time"
)
func func1(c chan bool) {
fmt.Println("func1 init start, time=", time.Now().UnixMilli())
// 用sleep模拟初始化工作
time.Sleep(time.Second)
fmt.Println("func1 init end, time=", time.Now().UnixMilli())
close(c)
time.Sleep(time.Second)
fmt.Println("func1 end, time=", time.Now().UnixMilli())
}
func main() {
fmt.Println("main start, time=", time.Now().UnixMilli())
c := make(chan bool, 1)
go func1(c)
// 开始阻塞等待
<-c
fmt.Println("main continue, time=", time.Now().UnixMilli())
time.Sleep(time.Second * 2)
}
技巧2:向无缓冲的chan中写数据会阻塞,直到另一方从该chan取走数据,写入方才会返回
举例:如果协程A希望协程B能立马处理自己的请求,应该使用无缓冲chan,让协程B在select中处理
常用于:一个协程希望另一个协程立即处理自己的请求
注意事项:
1. 因为会阻塞写入的协程,建议在单独协程中向无缓冲通道中写
2. 正常代码向无缓冲chan中写数据会阻塞,但是在select代码中向无缓冲chan中写数据不会阻塞写入的协程,写入的协程会继续执行其他代码,完全不影响,也就是下面要说的技巧3
代码实验:
1. 主协程创建一个子协程,在子协程的函数参数中传入一个无缓冲的chan
2. 主协程向这个chan写入数据,可以看到主协程阻塞
3. 子协程在休眠2秒后才从chan中读出来数据,可以发现主协程从阻塞中返回了
实验结果如下图(可以发现主协程在阻塞,子协程休眠结束从chan中读取数据后,主协程立马就从阻塞中返回了)
完整代码:
package main
import (
"fmt"
"time"
)
// 独立协程
func goroutine1(c chan int) {
// 休眠2秒
time.Sleep(time.Second * 2)
fmt.Println("子协程开始从c读取, time=", time.Now().Unix())
// 从c中读取
<-c
fmt.Println("子协程从c读取结束, time=", time.Now().Unix())
}
func main() {
c := make(chan int)
go goroutine1(c)
fmt.Println("主协程开始向c写入, time=", time.Now().Unix())
c <- 1
fmt.Println("主协程向c写入结束, time=", time.Now().Unix())
time.Sleep(time.Second * 3)
}
技巧3:在select中向无缓冲的chan中写数据不会阻塞写入协程
举例:如果协程A希望协程B能立马处理自己的请求,但是又不希望自己阻塞等待,应该使用无缓冲chan,让协程B在select中处理
常用于:一个协程希望另一个协程立即处理自己的请求,但自己不想阻塞等
注意事项:
写入一方要在select中写入
代码实验:
1. 主协程创建一个子协程,在子协程的函数参数中传入一个无缓冲的chan
2. 主协程在select中向这个chan写入数据,可以看到主协程没阻塞,该干嘛干嘛,定时器还在触发
3. 子协程每休眠3秒后才从chan中读出来数据,可以发现每次子协程读取完成一次,主协程的select都会返回一次
实验结果如下图(可以发现主协程完全没阻塞,定时器仍然在触发。而且每次子协程从chan中读取数据后,主协程立马能从select中返回了)
完整代码:
package main
import (
"fmt"
"time"
)
// 独立协程
func goroutine1(c chan int) {
for {
time.Sleep(time.Millisecond * 3500)
fmt.Println("子协程开始从无缓冲通道接收数据, time=", time.Now().UnixMilli())
<-c // 从无缓冲通道中读取
fmt.Println("子协程从无缓冲通道接收数据完成, time=", time.Now().UnixMilli())
}
}
func main() {
c := make(chan int)
go goroutine1(c)
syncTicker := time.NewTicker(time.Second)
for {
select {
case <-syncTicker.C:
{
fmt.Println("主协程的定时器触发, time=", time.Now().UnixMilli())
}
case c <- 1:
{
fmt.Println("主协程向无缓冲通道写入数据完成了, time=", time.Now().UnixMilli())
}
}
// 其他代码
// todo
fmt.Println("主线程跳出了select, time=", time.Now().UnixMilli())
}
}