• 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
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
  • 相关阅读:
    【MindSpore易点通机器人-03】迭代0的准备工作
    基于Vue+Element-Ui开发的月日组件,可以选择月份和天数小插件(新版本)
    JAXB实现JavaBean与XML相互转换(详尽)
    Java面试整理(二)《JavaSE》
    springboot+特色农产品电商平台 毕业设计-附源码211515
    Python入门(二十四)-文件操作2
    git commit 报错 “invalid path” “make_cache_entry failed for path” 解决方法
    安装淘宝镜像cnpm报错
    详细了解Redis的八种数据类型及应用场景分析
    LeetCode 1297. 子串的最大出现次数
  • 原文地址:https://blog.csdn.net/kina100/article/details/126687662