Go使用协程交替打印字符


Posted in Golang onApril 29, 2021

需求: 模拟两个协程,分别循环打印字母A和B。

分析: 要实现两个协程之间的交替协作,就必须用到channel通信机制,而channel正好是同步阻塞的。

半开方式

首先我们用一个channel变量来控制两个goroutine的交替打印:

func main() {
   exit := make(chan bool)
   ch1 := make(chan int)
   go func() {
      for i := 1; i <= 10; i++ {
         ch1 <- 0 //生产
         fmt.Println("A",i)
      }
      exit <- true
   }()
   go func() {
      for i := 1; i <= 10; i++ {
         <-ch1 //消费
         fmt.Println("B",i)
      }
   }()
   <-exit
}

结果发现打印出了ABBAABBA...的效果。

也就是我们控制了开始的次序,但没有控制结束的次序,发生了并发不安全的情况。

其实半开模式也可以用于某些场景下,如: 两个goroutine,在条件控制下,交替打印奇偶数:

func main() {
   exit := make(chan bool)
   ch1 := make(chan int)
   go func() {
      for i := 1; i <= 10; i++ {
         ch1 <- 0
         if i%2 == 0 {
            fmt.Println("A", i)
         }
      }
      exit <- true
   }()
   go func() {
      for i := 1; i <= 10; i++ {
         <-ch1
         if i%2 == 1 {
            fmt.Println("B", i)
         }
      }
   }()
   <-exit
}

封闭方式

接下来我们使用两个channel变量来模拟goroutine循环体的互斥问题。

func main() {
   exit := make(chan bool)
   ch1, ch2 := make(chan bool), make(chan bool)
   
   go func() {
      for i := 1; i <= 10; i++ {
         ch1 <- true
         fmt.Println("A", i)
         //在ch1和ch2之间是阻塞独占的
         <-ch2
      }
      exit <- true
   }()
   go func() {
      for i := 1; i <= 10; i++ {
         <-ch1
         fmt.Println("B", i)
         ch2 <- true
      }
   }()
   <-exit
}

我们在循环体首尾都使用了阻塞独占模式,两个chan交替释放控制权,达到了安全的协程交互控制。

再看看下面的Demo,同样的原理:

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
   }
}

缓冲模式

缓冲模式和封闭模式相似,只是封闭模式中,两个goroutine有明确的首尾角色。而缓冲模式的第一生产者交给了主协程,两个goroutine结构一样,轮式交换角色。

func main() {
   exit := make(chan bool)
   ch1, ch2 := make(chan bool,1), make(chan bool)
   ch1 <- true //生产(选择一个启动项)
   
   go func() {
      for i := 1; i <= 10; i++ {
         if ok := <-ch1; ok { //消费
            fmt.Println("A", 2*i-1)
            ch2 <- true //生产
         }
      }
   }()
   go func() {
      defer func() { close(exit) }()
      for i := 1; i <= 10; i++ {
         if ok := <-ch2; ok { //消费
            fmt.Println("B", 2*i)
            ch1 <- true //生产
         }
      }
   }()
   <-exit
}

结论:

Channel的本质就是同步式的生产消费模式

补充:go 让N个协程交替打印1-100

今天遇到一道面试题,开启N个协程,并交替打印1-100如给定N=3则输出:

goroutine0: 0

goroutine1: 1

goroutine2: 2

goroutine0: 3

goroutine1: 4

面试时没答案,虽过后研究参考了一些网上方法,并记录下来,先上代码

func print() {
	chanNum := 3                           // chan 数量
	chanQueue := make([]chan int, chanNum) // 创建chan Slice
	var result = 0                         // 值
	exitChan := make(chan bool)            // 退出标识
	for i := 0; i < chanNum; i++ {
		//	创建chan
		chanQueue[i] = make(chan int)
		if i == chanNum-1 {
			//	给最后一个chan写一条数据,为了第一次输出从第1个chan输出
			go func(i int) {
				chanQueue[i] <- 1
			}(i)
		}
	}
	for i := 0; i < chanNum; i++ {
		var lastChan chan int //    上一个goroutine 结束才能输出 控制输出顺序
		var curChan chan int  //	当前阻塞输出的goroutine
		if i == 0 {
			lastChan = chanQueue[chanNum-1]
		} else {
			lastChan = chanQueue[i-1]
		}
		curChan = chanQueue[i]
		go func(i int, lastChan, curChan chan int) {
			for {
				if result > 100 {
					//	超过100就退出
					exitChan <- true
				}
				//	一直阻塞到上一个输出完,控制顺序
				<-lastChan
				fmt.Printf("thread%d: %d \n", i, result)
				result = result + 1
				//	当前goroutine已输出
				curChan <- 1
			}
		}(i, lastChan, curChan)
	}
	<-exitChan
	fmt.Println("done")
}

1、第一个for循环创建chan

2、第二个for循环里的lastChan意思是,当前chan如果要打印数据,就必须得上一个chan打印完后才能打印。

这里假设N=2,chan索引为0,1,当索引1要输出,就阻塞到索引0的chan有数据为止,当自己打印完后往自己的chan中发送一个1,方便给依赖自己的chan 解除阻塞。

这里有个特殊的地方,当索引为0时,他的依赖索引chan就为chanQueue的长度-1,如果没有在创建Chan中的时候没有下面这一串代码就会造成死锁

if i == chanNum-1 {
 // 给最后一个chan写一条数据,为了第一次输出从第1个chan输出
 go func(i int) {
 chanQueue[i] <- 1
 }(i)
}

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

Golang 相关文章推荐
golang通过递归遍历生成树状结构的操作
Apr 28 Golang
golang goroutine顺序输出方式
Apr 29 Golang
Go语言 go程释放操作(退出/销毁)
Apr 30 Golang
Go标准容器之Ring的使用说明
May 05 Golang
完美解决golang go get私有仓库的问题
May 05 Golang
golang 实用库gotable的具体使用
Jul 01 Golang
Go 语言中 20 个占位符的整理
Oct 16 Golang
深入理解go缓存库freecache的使用
Feb 15 Golang
Golang 并发下的问题定位及解决方案
Mar 16 Golang
golang使用map实现去除重复数组
Apr 14 Golang
Go web入门Go pongo2模板引擎
May 20 Golang
GO中sync包自由控制并发示例详解
Aug 05 Golang
golang goroutine顺序输出方式
Apr 29 #Golang
golang 在windows中设置环境变量的操作
解决golang在import自己的包报错的问题
golang import自定义包方式
golang 接口嵌套实现复用的操作
Apr 29 #Golang
浅谈Golang 嵌套 interface 的赋值问题
Apr 29 #Golang
Go 实现英尺和米的简单单位换算方式
Apr 29 #Golang
You might like
全国FM电台频率大全 - 5 内蒙古自治区
2020/03/11 无线电
咖啡知识 除了喝咖啡还有那些知识点
2021/03/06 新手入门
修改php.ini实现Mysql导入数据库文件最大限制的修改方法
2007/12/11 PHP
采用header定义为文件然后readfile下载(隐藏下载地址)
2014/01/31 PHP
JavaScript自执行闭包的小例子
2013/06/29 Javascript
Javascript中获取对象的原型对象的方法小结
2015/02/25 Javascript
jquery合并表格中相同文本的相邻单元格
2015/07/17 Javascript
深入分析jsonp协议原理
2015/09/26 Javascript
jQuery Validate 校验多个相同name的方法
2017/05/18 jQuery
Vue DevTools调试工具的使用
2017/12/05 Javascript
ES6中的class是如何实现的(附Babel编译的ES5代码详解)
2019/05/17 Javascript
微信小程序实现搜索功能并跳转搜索结果页面
2019/05/18 Javascript
[07:47]DOTA2国际邀请赛采访专栏:探访Valve总部
2013/08/08 DOTA
[45:46]2014 DOTA2国际邀请赛中国区预选赛5.21 HGT VS DT
2014/05/23 DOTA
[07:06]2018DOTA2国际邀请赛寻真——卫冕冠军Team Liquid
2018/08/10 DOTA
使用Python简单的实现树莓派的WEB控制
2016/02/18 Python
Python中正则表达式的用法总结
2019/02/22 Python
Python3利用print输出带颜色的彩色字体示例代码
2019/04/08 Python
python3 自动识别usb连接状态,即对usb重连的判断方法
2019/07/03 Python
pandas将多个dataframe以多个sheet的形式保存到一个excel文件中
2019/10/10 Python
解决Numpy中sum函数求和结果维度的问题
2019/12/06 Python
python GUI框架pyqt5 对图片进行流式布局的方法(瀑布流flowlayout)
2020/03/12 Python
python如何建立全零数组
2020/07/19 Python
Python如何发送与接收大型数组
2020/08/07 Python
细说CSS3中的选择符
2008/10/17 HTML / CSS
使用css3背景渐变中的透明度来设置不同颜色的背景渐变
2014/03/31 HTML / CSS
荷兰优雅女装网上商店:Heine
2016/11/14 全球购物
Notino匈牙利:购买香水和化妆品
2019/04/12 全球购物
汽车检测与维修专业求职信
2013/10/30 职场文书
开水果连锁店创业计划书
2013/12/29 职场文书
音乐之声音乐广播稿
2014/09/10 职场文书
2014学习优秀共产党员先进事迹材料思想汇报
2014/09/14 职场文书
代办出身证明书
2014/10/21 职场文书
2014年消防工作总结
2014/11/21 职场文书
2015年财务人员个人工作总结
2015/07/27 职场文书
学校2016年九九重阳节活动总结
2016/04/01 职场文书