• 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
  • 相关阅读:
    阿里云全站加速 DCDN 重磅发布!打造新一代加速引擎
    45从零开始用Rust编写nginx,静态文件服务器竟然还有这些细节
    muc和soc的区别与联系
    react实现websocket消息推送
    PostgreSQL数据库配置文件
    层次式架构的设计理论与实践
    LeetCode:第305场周赛【总结】
    如何编写测试团队通用的Jmeter脚本
    Kotlin语法入门-密封类和密封接口(11)
    代码随想录 10.14 || 二叉树 LeetCode 669.修剪二叉搜索树、108.将有序数组转换为二叉搜索树、538.将二叉搜索树转为累加树
  • 原文地址:https://blog.csdn.net/kina100/article/details/126687662