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 相关文章推荐
为什么不建议在go项目中使用init()
Apr 12 Golang
使用Golang的channel交叉打印两个数组的操作
Apr 29 Golang
go语言中GOPATH GOROOT的作用和设置方式
May 05 Golang
Golang: 内建容器的用法
May 05 Golang
完美解决golang go get私有仓库的问题
May 05 Golang
Go 在 MongoDB 中常用查询与修改的操作
May 07 Golang
Go 语言结构实例分析
Jul 04 Golang
Go 通过结构struct实现接口interface的问题
Oct 05 Golang
Go语言基础切片的创建及初始化示例详解
Nov 17 Golang
GO语言字符串处理函数之处理Strings包
Apr 14 Golang
Go语言测试库testify使用学习
Jul 23 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
php下判断数组中是否存在相同的值array_unique
2008/03/25 PHP
PHP n个不重复的随机数生成代码
2009/06/23 PHP
header中Content-Disposition的作用与使用方法
2012/06/13 PHP
thinkphp数据查询和遍历数组实例
2014/11/28 PHP
php基础设计模式大全(注册树模式、工厂模式、单列模式)
2015/08/31 PHP
PHP查询分页的实现代码
2017/06/09 PHP
ThinkPHP框架整合微信支付之Native 扫码支付模式二图文详解
2019/04/09 PHP
php layui实现前端多图上传实例
2019/07/30 PHP
最简单的jQuery程序 入门者学习
2009/07/09 Javascript
js对象的构造和继承实现代码
2010/12/05 Javascript
jcarousellite.js 基于Jquery的图片无缝滚动插件
2010/12/30 Javascript
jQuery的.live()和.die() 使用介绍
2011/09/10 Javascript
webapp框架AngularUI的demo改造之路
2014/12/21 Javascript
JS+DIV实现鼠标划过切换层效果的方法
2015/05/25 Javascript
JavaScript中split与join函数的进阶使用技巧
2016/05/03 Javascript
jQuery.cookie.js使用方法及相关参数解释
2017/03/06 Javascript
微信小程序实战之自定义模态弹窗(8)
2017/04/18 Javascript
angular.js + require.js构建模块化单页面应用的方法步骤
2017/07/19 Javascript
Vue2.0如何发布项目实战
2017/07/27 Javascript
layui多图上传实现删除功能的例子
2019/09/23 Javascript
axios 实现post请求时把对象obj数据转为formdata
2019/10/31 Javascript
JavaScript Dom实现轮播图原理和实例
2021/02/19 Javascript
[01:07:20]DOTA2-DPC中国联赛 正赛 Dynasty vs XG BO3 第二场 2月2日
2021/03/11 DOTA
Linux(Redhat)安装python3.6虚拟环境(推荐)
2018/05/05 Python
Pycharm设置utf-8自动显示方法
2019/01/17 Python
Python列表(List)知识点总结
2019/02/18 Python
python 使用装饰器并记录log的示例代码
2019/07/12 Python
Python 操作 ElasticSearch的完整代码
2019/08/04 Python
Django自定义用户表+自定义admin后台中的字段实例
2019/11/18 Python
python 批量下载bilibili视频的gui程序
2020/11/20 Python
4s店总经理岗位职责
2013/12/31 职场文书
赡养老人协议书
2014/04/21 职场文书
公司总经理助理岗位职责
2014/07/09 职场文书
大学毕业典礼演讲稿
2014/09/09 职场文书
美术教师个人工作总结
2015/02/06 职场文书
Sql Server 行数据的某列值想作为字段列显示的方法
2022/04/20 SQL Server