使用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语言map与string的相互转换的实现
Apr 07 Golang
Go语言操作数据库及其常规操作的示例代码
Apr 21 Golang
Go缓冲channel和非缓冲channel的区别说明
Apr 25 Golang
基于Go Int转string几种方式性能测试
Apr 28 Golang
Golang 使用Map实现去重与set的功能操作
Apr 29 Golang
golang中的并发和并行
May 08 Golang
Golang标准库syscall详解(什么是系统调用)
May 25 Golang
详解Golang如何优雅的终止一个服务
Mar 21 Golang
Go并发4种方法简明讲解
Apr 06 Golang
golang用type-switch判断interface的实际存储类型
Apr 14 Golang
Golang bufio详细讲解
Apr 21 Golang
GO中sync包自由控制并发示例详解
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 动态添加记录
2009/03/10 PHP
PHP APC缓存配置、使用详解
2014/03/06 PHP
php顺序查找和二分查找示例
2014/03/27 PHP
php使用PDO方法详解
2014/12/27 PHP
CodeIgniter表单验证方法实例详解
2016/03/03 PHP
5分钟理解JavaScript中this用法分享
2013/11/09 Javascript
javascript运行机制之this详细介绍
2014/02/07 Javascript
jQuery ajax时间差导致的变量赋值问题分析
2016/01/22 Javascript
jQuery修改DOM结构_动力节点Java学院整理
2017/07/05 jQuery
seajs中最常用的7个功能、配置示例
2017/10/10 Javascript
angular项目中bootstrap-datetimepicker时间插件的使用示例
2018/03/15 Javascript
js+css实现打字效果
2020/06/24 Javascript
浅析node.js的模块加载机制
2018/05/25 Javascript
vue实现点击关注后及时更新列表功能
2018/06/26 Javascript
VUE2.0 ElementUI2.0表格el-table自适应高度的实现方法
2018/11/28 Javascript
JavaScript onclick事件使用方法详解
2020/05/15 Javascript
[00:32]DOTA2上海特级锦标赛 Ehome战队宣传片
2016/03/03 DOTA
[52:44]VGJ.T vs infamous Supermajor小组赛D组败者组第一轮 BO3 第一场 6.3
2018/06/04 DOTA
Python中装饰器兼容加括号和不加括号的写法详解
2017/07/05 Python
用Python写一段用户登录的程序代码
2018/04/22 Python
Tensorflow实现卷积神经网络的详细代码
2018/05/24 Python
python 读取目录下csv文件并绘制曲线v111的方法
2018/07/06 Python
Python3自动签到 定时任务 判断节假日的实例
2018/11/13 Python
python采集微信公众号文章
2018/12/20 Python
python实现桌面托盘气泡提示
2019/07/29 Python
python3中编码获取网页的实例方法
2020/11/16 Python
canvas 阴影和图形变换的示例代码
2018/01/02 HTML / CSS
《长城》教学反思
2014/02/14 职场文书
教师个人读书活动总结
2014/07/08 职场文书
师德承诺书2015
2015/04/28 职场文书
十七岁的单车观后感
2015/06/12 职场文书
销售口号霸气押韵
2015/12/24 职场文书
执行力心得体会范文
2016/01/11 职场文书
python 网络编程要点总结
2021/06/18 Python
Spring Cloud Netflix 套件中的负载均衡组件 Ribbon
2022/04/13 Java/Android
如何使用python包中的sched事件调度器
2022/04/30 Python