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语言操作数据库及其常规操作的示例代码
Apr 21 Golang
golang中的空slice案例
Apr 27 Golang
Golang 空map和未初始化map的注意事项说明
Apr 29 Golang
Go使用协程交替打印字符
Apr 29 Golang
golang DNS服务器的简单实现操作
Apr 30 Golang
解决golang结构体tag编译错误的问题
May 02 Golang
golang日志包logger的用法详解
May 05 Golang
Go 自定义package包设置与导入操作
May 06 Golang
Go语言应该什么情况使用指针
Jul 25 Golang
golang实现浏览器导出excel文件功能
Mar 25 Golang
Go语言安装并操作redis的go-redis库
Apr 14 Golang
基于Python实现西西成语接龙小助手
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
PHP中Date获取时间不正确怎么办
2008/06/05 PHP
ThinkPHP使用PHPExcel实现Excel数据导入导出完整实例
2014/07/22 PHP
php命令行用法入门实例教程
2014/10/27 PHP
php实现XSS安全过滤的方法
2015/07/29 PHP
CI框架使用composer安装的依赖包步骤与方法分析
2016/11/21 PHP
Thinkphp5.0 框架实现控制器向视图view赋值及视图view取值操作示例
2019/10/12 PHP
Mootools 1.2教程 设置和获取样式表属性
2009/09/15 Javascript
jquery 添加节点的几种方法介绍
2013/09/04 Javascript
纯JavaScript实现的兼容各浏览器的添加和移除事件封装
2015/03/28 Javascript
JS中Array数组学习总结
2017/01/18 Javascript
Angularjs 双向绑定时字符串的转换成数字类型的问题
2017/06/12 Javascript
JavaScript利用fetch实现异步请求的方法实例
2017/07/26 Javascript
angular.extend方法的具体使用
2017/09/14 Javascript
基于Bootstrap表单验证功能
2017/11/17 Javascript
webpack4 从零学习常用配置(小结)
2019/05/28 Javascript
Vue实现多标签选择器
2019/11/28 Javascript
[01:16:50]DOTA2-DPC中国联赛 正赛 Phoenix vs CDEC BO3 第一场 3月7日
2021/03/11 DOTA
用Python生成器实现微线程编程的教程
2015/04/13 Python
Python2.x和3.x下maketrans与translate函数使用上的不同
2015/04/13 Python
python实现读取excel写入mysql的小工具详解
2017/11/20 Python
2018年Python值得关注的开源库、工具和开发者(总结篇)
2018/01/04 Python
python实现点对点聊天程序
2018/07/28 Python
python调用webservice接口的实现
2019/07/12 Python
Python使用微信itchat接口实现查看自己微信的信息功能详解
2019/08/22 Python
python实现tail实时查看服务器日志示例
2019/12/24 Python
python GUI库图形界面开发之PyQt5布局控件QHBoxLayout详细使用方法与实例
2020/03/06 Python
使用pymysql查询数据库,把结果保存为列表并获取指定元素下标实例
2020/05/15 Python
HTML5 Convas APIs方法详解
2015/04/24 HTML / CSS
应届生污水处理求职信
2013/11/06 职场文书
自动化毕业生专业自荐书范文
2014/02/04 职场文书
《跟踪台风的卫星》教学反思
2014/04/10 职场文书
2014年检验员工作总结
2014/11/19 职场文书
停发工资证明范本
2015/06/12 职场文书
党员反腐倡廉学习心得体会
2015/08/15 职场文书
房产销售员2015年终工作总结
2015/10/22 职场文书
入党转正申请自我鉴定
2019/06/25 职场文书