使用Golang的channel交叉打印两个数组的操作


Posted in Golang onApril 29, 2021

Go的channel提供了强大的同步功能,那么如何使用channel交叉打印两个数组呢?

灰常简单,只需设置两个channel变量

数组1打印完一个值就用channel通知数组2,同理数组2打印完一个值用另一个channel通知数组1,即可实现同步

package main 
import "fmt" 
func main(){
 ch1 :=make(chan int)
 ch2 :=make(chan string)
 str :=[5]string{"a","b","c","d","e"}
 go func() {
  for i:=0;i<5;i++{
   ch1<-i
   fmt.Print(i+1)
   <-ch2
  }
 }()
 
 for _,v :=range str{
  <-ch1
  fmt.Print(v)
  ch2<-v
 }
}

结果:

1a2b3c4d5e

Process finished with exit code 0

补充:使用golang的channel的坑

很多时候我们经过使用有缓冲channel作为通信控制的功能,以至有一些误解和坑出现。

误解一:有缓存channel是顺序的

执行下面代码:

package mainimport (    "time"
    "math/rand")func main(){
    cache:=make(chan int,4)    go func() {        for i:=0;i< 10;i++ {
            cache<-i
        }
    }()    go getCache(cache)    go getCache(cache)    go getCache(cache)
    time.Sleep(3*time.Second)
}func getCache(cache <-chan int)  {    for  {        select {        case i:=<-cache:            println(i)
            time.Sleep(time.Duration(rand.Int31n(100))*time.Millisecond)
        }
    }
}

多执行几次看看结果,并不是每一次都是可以顺序输出的,有缓存channel是乱序的。因为这里让一些同学误解了,我在此多解释一下。

针对通道的发送和接收操作都是可能造成相关的goroutine阻塞。

试想一下,有多个goroutine向同一个channel发送数据而被阻塞,如果还channel有多余的缓存空间时候,最早被阻塞的goroutine会最先被唤醒。

也就是说,这里的唤醒顺序与发送操作的开始顺序是一致的,对接收操作而言亦为如此。无论是发送还是接收操作,运行时系统每次只会唤醒一个goroutine。

而这里的乱序是指,如果像使用channel缓存中多个goroutine实现顺序是正确的,因为每一个goroutine抢到处理器的时间点不一致,所以不能保证顺序。

误解二:channel缓存的大小就是并发度

如下代码:

package mainimport ( "fmt"
 "sync"
 "time")var wg = sync.WaitGroup{}func main() {
 wg.Add(2)
 bf := make(chan string, 64) go insert(bf) go get(bf)
 wg.Wait()
}func insert(bf chan string) {
 str := "CockroachDB 的技术选型比较激进,比如依赖了 HLC 来做事务的时间戳。但是在 Spanner 的事务模型的 Commit Wait 阶段等待时间的选择,CockroachDB 并没有办法做到 10ms 内的延迟;CockroachDB 的 Commit Wait 需要用户自己指定,但是谁能拍胸脯说 NTP 的时钟误差在多少毫秒内?我个人认为在处理跨洲际机房时钟同步的问题上,基本只有硬件时钟一种办法。HLC 是没办法解决的。另外 Cockroach 采用了 gossip 来同步节点信息,当集群变得比较大的时候,gossip 心跳会是一个非常大的开销。当然 CockroachDB 的这些技术选择带来的优势就是非常好的易用性,所有逻辑都在一个 binary 中,开箱即用,这个是非常大的优点。"
 for i := 0; i < 10000000; i++ {
  bf <- fmt.Sprintf("%s%d", str, i)
 }
 wg.Done()
}func sprint(s string) {
 time.Sleep(1000 * time.Millisecond)
}func get(bf chan string) { for {  go func() {   select {   case str := <-bf:
    sprint(str)   case <-time.After(3 * time.Second):
    wg.Done()
   }
  }()
 }
}

很多同学乍一看以为定义了

bf := make(chan string, 64)

就是说该程序的并发度控制在了64,执行就会发现内存一直在增长。

因为get()函数中启动的goroutine会越来越多,因为get()每读取一个数据,insert()就会往channel插入一条数据,此时并发度就不是64了。

需要修改为:

package mainimport ( "fmt"
 "sync"
 "time")var wg = sync.WaitGroup{}func main() {
 wg.Add(2)
 bf := make(chan string, 64) go insert(bf) //go get(bf)
    for i:=0;i<64;i++ {        go get1(bf)
    }
 wg.Wait()
}func insert(bf chan string) {
 str := "CockroachDB 的技术选型比较激进,比如依赖了 HLC 来做事务的时间戳。但是在 Spanner 的事务模型的 Commit Wait 阶段等待时间的选择,CockroachDB 并没有办法做到 10ms 内的延迟;CockroachDB 的 Commit Wait 需要用户自己指定,但是谁能拍胸脯说 NTP 的时钟误差在多少毫秒内?我个人认为在处理跨洲际机房时钟同步的问题上,基本只有硬件时钟一种办法。HLC 是没办法解决的。另外 Cockroach 采用了 gossip 来同步节点信息,当集群变得比较大的时候,gossip 心跳会是一个非常大的开销。当然 CockroachDB 的这些技术选择带来的优势就是非常好的易用性,所有逻辑都在一个 binary 中,开箱即用,这个是非常大的优点。"
 for i := 0; i < 10000000; i++ {
  bf <- fmt.Sprintf("%s%d", str, i)
 }
 wg.Done()
}func sprint(s string) {
 time.Sleep(1000 * time.Millisecond)
}func get1(bf chan string)  {    for {        select {        case str := <-bf:
            sprint(str)        case <-time.After(3 * time.Second):
            wg.Done()
        }
    }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

Golang 相关文章推荐
一文读懂go中semaphore(信号量)源码
Apr 03 Golang
Go语言使用select{}阻塞main函数介绍
Apr 25 Golang
golang中切片copy复制和等号复制的区别介绍
Apr 27 Golang
Go遍历struct,map,slice的实现
Jun 13 Golang
浅谈Go语言多态的实现与interface使用
Jun 16 Golang
修改并编译golang源码的操作步骤
Jul 25 Golang
Golang表示枚举类型的详细讲解
Sep 04 Golang
Go 语言中 20 个占位符的整理
Oct 16 Golang
Golang使用Panic与Recover进行错误捕获
Mar 22 Golang
GoFrame gredis缓存DoVar Conn连接对象 自动序列化GoFrame gredisDo/DoVar方法Conn连接对象自动序列化/反序列化总结
Jun 14 Golang
在ubuntu下安装go开发环境的全过程
Aug 05 Golang
Go语言编译原理之源码调试
Aug 05 Golang
Go使用协程交替打印字符
Apr 29 #Golang
golang goroutine顺序输出方式
Apr 29 #Golang
golang 在windows中设置环境变量的操作
解决golang在import自己的包报错的问题
golang import自定义包方式
golang 接口嵌套实现复用的操作
Apr 29 #Golang
浅谈Golang 嵌套 interface 的赋值问题
Apr 29 #Golang
You might like
防止用户利用PHP代码DOS造成用光网络带宽
2011/03/01 PHP
PHP查询MySQL大量数据的时候内存占用分析
2011/07/22 PHP
解析php mysql 事务处理回滚操作(附实例)
2013/08/05 PHP
PHP中对各种加密算法、Hash算法的速度测试对比代码
2014/07/08 PHP
destoon实现公司新闻详细页添加评论功能的方法
2014/07/15 PHP
PHP实现创建一个RPC服务操作示例
2020/02/23 PHP
Javascript与vbscript数据共享
2007/01/09 Javascript
Jquery在IE7下无法使用 $.ajax解决方法
2009/11/11 Javascript
JSON 和 JavaScript eval使用说明
2010/06/13 Javascript
jquery下利用jsonp跨域访问实现方法
2010/07/29 Javascript
jQuery中$.fn的用法示例介绍
2013/11/05 Javascript
js 自带的sort() 方法全面了解
2016/08/16 Javascript
微信小程序 animation API详解及实例代码
2016/10/08 Javascript
Web前端框架bootstrap实战【第一次接触使用】
2016/12/28 Javascript
JS/HTML5游戏常用算法之追踪算法实例详解
2018/12/12 Javascript
实例介绍JavaScript中多种组合继承
2019/01/20 Javascript
Linux中安装Python的交互式解释器IPython的教程
2016/06/13 Python
在tensorflow中实现屏蔽输出的log信息
2020/02/04 Python
python Timer 类使用介绍
2020/12/28 Python
国际化的太阳镜及太阳镜配件零售商:Sunglass Hut
2016/07/26 全球购物
世界最大的私人旅行指南出版商:孤独星球
2016/08/23 全球购物
环境科学专业个人求职信
2013/12/15 职场文书
高中毕业自我鉴定
2013/12/22 职场文书
党校学习思想汇报
2014/01/06 职场文书
致铅球运动员加油稿
2014/02/13 职场文书
期末学生评语大全
2014/04/24 职场文书
保护黄河倡议书
2014/05/16 职场文书
2014年党支部承诺书
2014/05/30 职场文书
2015年世界水日活动总结
2015/02/09 职场文书
2015年五四青年节演讲稿
2015/03/18 职场文书
工作会议通知
2015/04/15 职场文书
2015年党小组工作总结
2015/05/26 职场文书
上学路上观后感
2015/06/16 职场文书
2016年妇联“6﹒26国际禁毒日”宣传活动总结
2016/04/05 职场文书
导游词之河姆渡遗址博物馆
2019/10/10 职场文书
SQL Server中使用判断语句(IF ELSE/CASE WHEN )案例
2021/07/07 SQL Server