手机网站开发的目的及定位,创建一个网站主页,网页制作与网站建设英文翻译,上海互联网网站建设channel 是什么#xff1f;
channel 是GO语言中一种特殊的类型#xff0c;是连接并发goroutine的管道
channel 通道是可以让一个 goroutine 协程发送特定值到另一个 goroutine 协程的通信机制。
关于 channel 的原理#xff0c;channel通道需要注意的地方#xff0c;之前…channel 是什么
channel 是GO语言中一种特殊的类型是连接并发goroutine的管道
channel 通道是可以让一个 goroutine 协程发送特定值到另一个 goroutine 协程的通信机制。
关于 channel 的原理channel通道需要注意的地方之前有分享过可以查看如下文章
GO通道和 sync 包的分享GO 中 channel 实现原理
本次我们主要分享的是关于 nil channel 通道有缓冲通道无缓冲通道 的常用方法以及巧妙使用的方式
巧用 nil 的 channel 通道
平日里使用的 channel 通道都是使用无缓冲或者有缓冲的 channel 通道或许使用为 nil 的 channel 通道还是比较少甚至都不知道如何去使用 nil 的 channel 通道
我们先来看这么一个例子
创建两个 channel c1, c2 数据类型是 struct{} 用于占位分别开辟两个子协程其中子协程 1 在 2 秒之后写入数据给到 c1另外一个子协程 2 在 1 秒之后写入数据给到 c2主协程循环等待阻塞读取 c1 , c2 里面的数据读取后将对应的标识 ok1 / ok2 置为 true当 ok1 和 ok2 都为 true 的时候退出循环结束程序
func main() {c1, c2 : make(chan struct{}), make(chan struct{})go func() {time.Sleep(time.Second * 2)c1 - struct{}{}//close(c1)}()go func() {time.Sleep(time.Second * 1)c2 - struct{}{}// close(c2)}()var (ok1 boolok2 bool)for {select {case -c1:ok1 truefmt.Println(1)case -c2:ok2 truefmt.Println(2)}if ok1 ok2 {break}}fmt.Println(program termination ... )
}运行结果如下
2
1
program termination ...看上去效果一切正常若此时我们将上述代码中的 close(c1) 和 close(c2) 的注释去掉我们再查看一下结果就回是这样的
...
2
2
2
1
program termination ...出现这样的问题是什么呢是因为我们 close channel 通道之后若还对这个通道写入数据会 panic若还从这个通道读取数据会立即返回该通道类型的零值而不会阻塞等待数据
因此才会有上述情况那么这个时候我就可以很好的用好这个 nil 的 channel咱就可以这样来调整一下关于通道使用的情况
修改为从通道中读取数据时先判断通道是否已经关闭若关闭则将通道设置为 nil若未关闭则打印我们从通道中读取的数据此处模拟直接打印一个固定的值
for {select {case _, ok : -c1:if !ok {c1 nil}else{fmt.Println(1)}case _, ok : -c2:if !ok {c2 nil}else{fmt.Println(2)}}if c1 nil c2 nil {break}
}这种时候我们就知道对于从通道中读取数据先去判断通道是否关闭若通道关闭了那么我们直接显示的给通道设置为 nil
这里是否会有这么一个疑问关闭通道通道变量不应该就变成 nil 了吗为什么我们还要自己去设置为 nil 实际上这就是我们对于通道的基础知识不扎实了关闭通道后通道本身并不会变为 nil。通道变量仍然持有通道的地址只是通道的状态变为了已关闭
巧用无缓冲 channel 通道
对于无缓冲的 channel 通道只有在对其进行接收操作的 goroutine 协程和对其进行发送操作的 goroutine 协程都存在的情况下通信才能进行否则单方面的操作会让对应的 goroutine 协程陷入阻塞状态因为该 channel 通道没有缓冲
使用无缓冲的 channel 通道我们可以用在如下几个方面
信号传递
信号传递我们就可以用在两个协程一对一的传递信号上面当然我们也可以使用在主协程主动通知所有子协程关闭的全场景下这就是一对多的传递信号相关的 demo 可以在这期文章中有展示
GO 语言的并发模式
一对一一个发一个收 一对多一个发多个收此处可以是 协程 1 close 掉 通道那么 多个协程默认都能够读取到通道的值是零值此时多个子协程就可以根据通道的关闭状态来处理后续的逻辑 控制同步
GO语言倡导我们不要通过共享内存来通信而应该通过通信来共享内存此处 channel 就是这样设计的当然如果需要有更高的性能那么我们还是可以使用更加低级的GO语言原语 sync 包中的锁机制
可以点击查看往期文章sync 锁机制 巧用有缓冲 channel 通道
用作队列
用作队列应该是比较好理解的队列先入先出 FIFO给 channel 通道设置明确的缓冲区例如 ch:make(chan int, 10) 多个协程就可以异步的并发处理该队列由于有缓冲的 channel 通道中有一定的容量因此对于协程读取通道中数据时存在阻塞的情况相对无缓冲的通道来说就会少很多相应的在一定程度上就提升了性能
对于有缓冲的 channel 通道channel 通道满的时候写入数据会阻塞读取数据正常处理 channel 通道空的时候写入数据正常读取数据会阻塞
用作信号量
有缓冲的 channel 通道还可以用来计数例如我们有 15 个 job可是目前只有 3 个 worker那么同一时间只会有 3 个worker 来干活我们就可以使用通道来查看目前有多少个 worker 在工作写一个简单的 demo
创建 j 和 worker channel 通道子协程 1 写 15 个任务给到 j 通道中写完 15 个任务到 j 中便关闭自己的通道因为后续我们需要使用 for…range 的方式读取通道使用 sync.WaitGroup 管控开辟的 3 个协程模拟 3 个 工人去干活能够从写入数据到 worker channel 通道中则开始干活干完之后从 worker channel 通道中读出数据
func main() {j : make(chan int, 15)worker : make(chan int, 3)go func() {for i : 0; i 15; i {j - i}close(j)}()var wg sync.WaitGroupfor job : range j {wg.Add(1)go func(job int) {defer wg.Done()worker - job// 模拟干活fmt.Println(正在执行 job : , job)time.Sleep(time.Second * 1)-worker}(job)}wg.Wait()fmt.Println(program termination ... )
}感兴趣的 xdm 的可以复制代码运行一下可以看到效果是 3 个 job 一起打印间隔 1 秒后又是 3 个 job 一起打印的
select 和 channel 通道如何结合使用
心跳
func main() {h : time.NewTicker(2 * time.Second)defer h.Stop()for {select {case -h.C:// 模拟处理心跳fmt.Println(hhh)}}
}使用 default
select ...default 这个组合就不必过多赘述了就是在我们阻塞读取通道数据时若当前时间没有从任何一个通道中读取到数据则默认走 default 里面的逻辑
超时机制
超时机制使用的也是非常频繁的很多时候为了方便可能我们会使用例如- time.After(10 * time.Second) 的方式使用这种方式GO 语言会维护一个最小堆当时间到了通道被唤醒的时候就会从最小堆顶取出 timer 对象再执行 timer 中的函数执行完毕之后自行就会做删除自行就会做 GC
可是在上述这种方式使用比较多的时候会给程序带来 GC 的压力我们完全可以入如下方式来实现超时机制显示的去做 GC
func main() {c : time.NewTimer(10 * time.Second)defer c.Stop()for {select {case -c.C:fmt.Println(program overtime )return}}
}总结
本次演示了关于 nil channel有缓冲 channel 无缓冲 channel select 如何与 channel 配合使用上述 demo 完全可以复制下来xdm 可以自行运行查看效果
欢迎点赞关注收藏
朋友们你的支持和鼓励是我坚持分享提高质量的动力 好了本次就到这里
技术是开放的我们的心态更应是开放的。拥抱变化向阳而生努力向前行。
我是阿兵云原生欢迎点赞关注收藏下次见~ 可以进入地址进行体验和学习https://xxetb.xet.tech/s/3lucCI