go实现N个协程交替顺序打印自然数的详细解释

这里主要用到了channel在没有缓冲区的时候的阻塞。如果我们每一个协程都生成一个管道,通知下一个协程什么时候能打印,打印哪个数字,这个问题就非常好解了。

主协程 第一个协程 第二个协程 第三个协程 firstChannle(主协程生成) nextChannel(第一个协程生成) nextChannel(第二个协程生成) 读取最后一个协程传回来的channel 放入firstChannel 通知第一个协程,解除阻塞 通知解除阻塞 nextChannel(第三个协程生成) 主协程 第一个协程 第二个协程 第三个协程

这里主要是形成一个闭环,每一个协程要打印的时机和数字是来自上一个协程的控制,最终有主协程(类似主持人角色)把最后一个协程的信号转发给第一个协程,从而形成闭环。


如果某一个协程发现打印的数字超过了设定的数字,这时候就会通知下一个协程close信号,然后下一个协程再通知下下个协程close,最后返回给主协程,类似计算机网络中的 环状网络。

package main

import (
   "fmt"
   "sync"
)

// go 通过goroutine 和 channel 实现交替打印并发控制


func main() {
   // 设定要起的协程数量
   var goroutineNum = 3
   // 最大打印整数
   var maxPrintNum = 30
   // 设定等待协程
   waitGoroutine := new(sync.WaitGroup)
   waitGoroutine.Add(goroutineNum)
   // 第一层管道,主要目的是把最后一协程生成的管道信号重新传递给第一个协程
   firstChannel := make(chan int)
   // 临时channel
   var temp chan int
   // 循环启动协程
   for i := 0; i < goroutineNum; i++ {
      // 每次循环把上一个goroutine里面的channel给带入到下一层
      if i == 0 {
         // 第一次是从主函数main生成的第一层channel
         temp = PrintNumber(firstChannel, i+1, maxPrintNum, waitGoroutine)
      } else {
         temp = PrintNumber(temp, i+1, maxPrintNum, waitGoroutine)
      }
   }
   // 第一层管道先增加一个量,从0开始计算
   firstChannel <- 0
   // 这里最终接受到的是 最后一个协程生成的 nextChannel
   for v := range temp {
      firstChannel <- v
   }
   close(firstChannel)
   waitGoroutine.Wait()
}

// PrintNumber 打印数字
// preChan 上一个协程生成的信号管道
// nowGoroutineFlag 当前协程标志,没啥用,就是为了看清打印的时候是哪个协程再打印
// maxNum 整个程序打印的最大数字
// wg 等待组,为了优雅退出
func PrintNumber(preChan chan int, nowGoroutineFlag int, maxNum int, wg *sync.WaitGroup) chan int {
   wg.Done()
   // 生成一个新的channel 用于给下一个goroutine传递信号
   nextChannel := make(chan int)
   // 把上一个goroutine的channel带入到新的协程里
   go func(preChan chan int) {
      // 上一个协程没有塞入数据之前这里是阻塞的
      for v := range preChan {
         // 如果上一个协程的channel发送了信号,这里将解除阻塞

         if v > maxNum {
            // 不再继续生成新的数字
            break
         } else {
            // 当前要打印的数字
            nowNum := v + 1
            // 打印当前协程标识和数字
            fmt.Printf("当前协程为第 %d 协程,当前数字为:%d \n", nowGoroutineFlag, nowNum)
            // 往下一个协程需要用到的channel里塞入信号
            nextChannel <- nowNum
         }
      }
      // 根据go的管道关闭原则,尽可能的在发送方关闭管道
      // 完成当前协程所有任务后,关闭管道
      close(nextChannel)
   }(preChan)
   return nextChannel
}


你可能感兴趣的:(golang笔记,golang,开发语言,后端)