golang channel的实现

88 阅读2分钟
  • channel结构

    • qcount:表明缓冲区元素的数量
    • datasiz:表明缓冲区的容量大小
    • elemtype、elemsize:chan的元素类型和元素大小,一旦声明就会固定下来
    • buf:存放元素的循环队列
    • sendx:循环队列发送元素的指针位置
    • recvx:循环队列接收元素的指针位置
    • sendq:buf满了再发送会阻塞当前协程,发送方阻塞队列
    • recvq:buf空接收元素会阻塞协程,接收方阻塞队列
    • closed:标志位,是否关闭
    • lock:锁,接收发送关闭通过加锁完成
  • send(发送):

    • 缓冲区空:

      • 如果recq有队列等待接收(代表buf中无数据),会直接把数据给到接收写成,不会放入到buf中,减少了已拷贝
      • 如果recq无队列,判断有无缓冲区
        • 有缓冲区,会把数据放入到buf中
        • 无缓冲区,会直接阻塞发送者
    • 缓冲区满:会阻塞发送者,把当前协程加入到sendq中

    • 如果chan已经close,在发送会报panic

  • recv(接受):

    • 无缓冲区:会直接阻塞调用
    • 有缓冲区:
      • buf中有数据,会优先从buf中取
      • buf中无数据,如果senq有等待队列,会直接从senq弹出一个sender,然后拷贝一个元素给到recv
      • buf中无数据,而且senq无等待队列,会被阻塞进入到revq中
      • 如果这个chan已经关闭,当buf中有元素的时候会直接返回buf元素和true,buf无元素,会返回elemtype累心的一个空值和false
  • close:close一个已经关闭的chabn会报panic

  • 具体应用

    • 数据传递:有 4 个 goroutine,编号为 1、2、3、4。每秒钟会有一个 goroutine 打印出它自己的编号,要求你编写程序,让输出的编号总是按照 1、2、3、4、1、2、3、4……这个顺序打印出来。

    • 消息交流:chanel就是一个生产/消费者的模型,可以使用这个模拟一个消息队列

    • 信号传递:最典型的是main方法里,通过接收关闭程序的信号量来实现优雅启停

              sigChan := make(chan os.Signal, 1)
              // 监听 SIGINT、SIGTERM 和 SIGHUP 信号
              signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
      
              // 等待信号
              sig := <-sigChan
              fmt.Printf("Received signal: %v\n", sig)
      
    • 任务编排:类似击鼓传花,一个groutine会完成部分任务,通过多个goroutine的协作,完成一个复杂的任务