Go使用协程交替打印字符

需求: 模拟两个协程,分别循环打印字母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)
}

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

(0)

相关推荐

  • golang 两个go程轮流打印一个切片的实现

    问题描述: 两个 go 程轮流打印一个切片. Golang 实现: 使用两个 channel,只用来判断 package main import ( "fmt" "sync" ) // 两个 go 程轮流打印一个切片 func main() { ch1 := make(chan bool, 1) ch2 := make(chan bool, 1) ch1 <- true nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} var

  • 浅谈golang for 循环中使用协程的问题

    两个例子 package main import ( "fmt" "time" ) func Process1(tasks []string) { for _, task := range tasks { // 启动协程并发处理任务 go func() { fmt.Printf("Worker start process task: %s\n", task) }() } } func main() { tasks := []string{&quo

  • go 协程返回值处理操作

    我就废话不多说了,大家还是直接看代码吧~ package main import "fmt" import "sync" var ch = make(chan int) func do(lock *sync.Mutex, ct *int) { lock.Lock() *ct++ lock.Unlock() ch <- 1 } func main() { fmt.Println("hello thread") var ct = 0 lock

  • Golang 之协程的用法讲解

    一.Golang 线程和协程的区别 备注:需要区分进程.线程(内核级线程).协程(用户级线程)三个概念. 进程.线程 和 协程 之间概念的区别 对于 进程.线程,都是有内核进行调度,有 CPU 时间片的概念,进行 抢占式调度(有多种调度算法) 对于 协程(用户级线程),这是对内核透明的,也就是系统并不知道有协程的存在,是完全由用户自己的程序进行调度的,因为是由用户程序自己控制,那么就很难像抢占式调度那样做到强制的 CPU 控制权切换到其他进程/线程,通常只能进行 协作式调度,需要协程自己主动把控

  • go获取协程(goroutine)号的实例

    我就废话不多说了,大家还是直接看代码吧~ func GetGID() uint64 { b := make([]byte, 64) b = b[:runtime.Stack(b, false)] b = bytes.TrimPrefix(b, []byte("goroutine ")) b = b[:bytes.IndexByte(b, ' ')] n, _ := strconv.ParseUint(string(b), 10, 64) return n } 补充:Go语言并发协程Go

  • 使用golang实现在屏幕上打印进度条的操作

    GoSimplePrint是一款用go写的开源简单进度条打印包.我可以利用它,在自己项目中需要加入进度条功能. 1.安装 go get -u github.com/redmask-hb/GoSimplePrint/goPrint 2.初始化 bar:=goPrint.NewBar(20) 这里的20,是我们满进度条的数值.如果我们要让进度条走满的时候值是100,那么应该NewBar(100) 3.配置参数 (1)设置 bar.SetGraph(graph string)  //设置进度条图块,默

  • 浅谈go 协程的使用陷阱

    golang 语言协程 协程中使用全局变量.局部变量.指针.map.切片等作为参数时需要注意,此变量的值变化问题. 与for 循环,搭配使用更需谨慎. 1.内置函数时直接使用局部变量,未进行参数传递 func main() { for i := 0; i < 100; i++ { go func() { fmt.Println(i) }() } } 运行效果 func main() { for i := 0; i < 100; i++ { go func(i int) { fmt.Printl

  • Go使用协程交替打印字符

    需求: 模拟两个协程,分别循环打印字母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(&quo

  • go语言实现两个协程交替打印

    目录 方法一:使用两个channel 方法二 :使用一个channel 方法一:使用两个channel 这里channel CA 必须要有缓冲区,否则最后会报错 fatal error: all goroutines are asleep - deadlock! 这是因为无缓冲的通道只有在有接收方能够接收值的时候才能发送成功,否则会一直处于等待发送的阶段.因为最后交替运行完后没有协程可以接收CA通道中的数据,所以会一直阻塞发生死锁 package main import (     "fmt&q

  • python使用threading.Condition交替打印两个字符

    Python中使用threading.Condition交替打印两个字符的程序. 这个程序涉及到两个线程的的协调问题,两个线程为了能够相互协调运行,必须持有一个共同的状态,通过这个状态来维护两个线程的执行,通过使用threading.Condition对象就能够完成两个线程之间的这种协调工作. threading.Condition默认情况下会通过持有一个ReentrantLock来协调线程之间的工作,所谓可重入锁,是只一个可以由一个线程递归获取的锁,此锁对象会维护当前锁的所有者(线程)和当前所

  • Golang协程常见面试题小结

    目录 交替打印奇数和偶数 方法一:使用无缓冲的channel进行协程间通信 方法二:使用有缓冲的channel N个协程打印1到maxVal 交替打印字符和数字 交替打印字符串 方法一使用无缓冲的channel 三个协程打印ABC Channel练习 交替打印奇数和偶数 下面让我们一起来看看golang当中常见的算法面试题使用两个goroutine交替打印1-100之间的奇数和偶数, 输出时按照从小到大输出. 方法一:使用无缓冲的channel进行协程间通信 package main impor

  • Python 高级教程之线程进程和协程的代码解析

    目录 进程 进程 5 种基本状态 进程的特点 进程间数据共享 进程池 进程的缺点 线程 线程的定义 使用线程模块的简单示例 代码解析 协程 协程与线程 Python 协程 协程的执行 关闭协程 链接协程以创建管道 总结 进程 进程是指在系统中正在运行的一个应用程序,是 CPU 的最小工作单元. 进程 5 种基本状态 一个进程至少具有 5 种基本状态:初始态.就绪状态.等待(阻塞)状态.执行状态.终止状态. 初始状态:进程刚被创建,由于其他进程正占有CPU资源,所以得不到执行,只能处于初始状态.

  • Python协程操作之gevent(yield阻塞,greenlet),协程实现多任务(有规律的交替协作执行)用法详解

    本文实例讲述了Python 协程操作之gevent(yield阻塞,greenlet),协程实现多任务(有规律的交替协作执行)用法.分享给大家供大家参考,具体如下: 实现多任务:进程消耗的资源最大,线程消耗的资源次之,协程消耗的资源最少(单线程). gevent实现协程,gevent是通过阻塞代码(例如网络延迟等)来自动切换要执行的任务,所以在进行IO密集型程序时(例如爬虫),使用gevent可以提高效率(有效利用网络延迟的时间去执行其他任务). GIL(全局解释器锁)是C语言版本的Python

  • 详解php协程知识点

    多任务 (并行和并发) 在讲协程之前,先谈谈多进程.多线程.并行和并发. 对于单核处理器,多进程实现多任务的原理是让操作系统给一个任务每次分配一定的 CPU 时间片,然后中断.让下一个任务执行一定的时间片接着再中断并继续执行下一个,如此反复. 由于切换执行任务的速度非常快,给外部用户的感受就是多个任务的执行是同时进行的. 多进程的调度是由操作系统来实现的,进程自身不能控制自己何时被调度,也就是说: 进程的调度是由外层调度器抢占式实现的 而协程要求当前正在运行的任务自动把控制权回传给调度器,这样就

  • 详解Go多协程并发环境下的错误处理

    引言 在Go语言中,我们通常会用到panic和recover来抛出错误和捕获错误,这一对操作在单协程环境下我们正常用就好了,并不会踩到什么坑.但是在多协程并发环境下,我们常常会碰到以下两个问题.假设我们现在有2个协程,我们叫它们协程A和B好了: 如果协程A发生了panic,协程B是否会因为协程A的panic而挂掉? 如果协程A发生了panic,协程B是否能用recover捕获到协程A的panic? 答案分别是:会.不能. 那么下面我们来一一验证,并给出在具体的业务场景下的最佳实践. 问题一 如果

  • Python中gevent模块协程使用

    目录 背景 什么是协程? 什么是 gevent? 协程的例子 Q&A Q:gevent 无法捕获的耗时 A:猴子补丁 实践 异步 requests 请求 gevent 的锁 Tip 背景 因为 Python 线程的性能问题,在 Python 中使用多线程运行代码经常不能达到预期的效果.而实际开发中我们经常有高并发的需求,这就要求我们的代码在跑的更快的同时需要单位时间内执行更多的有效逻辑.减少无用的等待. 什么是协程? 我们可以认为线程是轻量级的进程,所以你也可以理解协程是轻量级的线程.协程即在一

  • GO语言协程创建使用并通过channel解决资源竞争

    目录 创建协程 主协程终止,子协程也终止 runtime包 Gosched让出CPU时间片 Goexit立即结束当前协程 GOMAXPROCS设置并行CPU核数最大值,并返回之前的值 runtime.NumGoroutine()获取当前运行中的goroutine数量 多任务资源竞争问题 通过channel解决资源竞争问题 主协程如何等其余协程完再退出 创建协程 goroutine是go的设计核心,就是协程主协程终止了,子协程也终止 package main import ( "fmt"

随机推荐