在介绍底层原理之前,我们先简单地介绍一下channel的使用方法,比如初始化,发送和接收消息,然后我们在分别介绍他们在底层是如何实现的。

使用

channel的常见使用方法如下。

  • 申明channel
 var stop chan int  

此时stop为nil

  • 初始化channel
 stop = make(chan int, 1)   
  • 向channel发送消息
 stop <- 10  
  • 从channel接收消息
 msg := <- stop  
  • 关闭channel
 close(stop)  
  • select 多路复用
 a := make(chan int)
b := make(chan string)
select {
  case msg := <- a:
  fmt.Printf("recv: %d\n", msg)
  case b <- 10:
  fmt.Print("send \n")
  default:
  fmt.Print("default")
}  
channel底层结构

上面我们简单回顾了channel的基本使用方法,我并没有说太多如何使用,比如buffer channel和no buffer channel的区别,还有阻塞等,相信读者基本都已经掌握了channel的使用,亟需一些对channel的深入理解。

我常常见到一些golang新手,在使用channel的时候会这样写,这有什么问题呢?

 type notify struct {
a int 
  b string
  c bool
  d []int
}
var stop = make(chan notify, 10000)  

我们先来看看底层结构,就知道这样写初始化有多离谱。channel在运行时是hchan,定义如下。

 type hchan struct {
    qcount   uint           // total data in the queue
    dataqsiz uint           // size of the circular queue
    buf      unsafe.Pointer // points to an array of dataqsiz elements
    elemsize uint16
    closed   uint32
    elemtype *_type // element type
    sendx    uint   // send index
    recvx    uint   // receive index
    recvq    waitq  // list of recv waiters
    sendq    waitq  // list of send waiters

    // lock protects all fields in hchan, as well as several
    // fields in sudogs blocked on this channel.
    //
    // Do not change another G's status while holding this lock
    // (in particular, do not ready a G), as this can deadlock
    // with stack shrinking.
    lock mutex
}  

我们先以下图来描述一下channel,然后我们就能了解每个字段是用来做什么的,相信这样可以更好地帮助读者理解。

可以看出,channel的结构是由一个环形队列buf来存储send到channel的数据。使用双向链表recvq和sendq来存储接收阻塞和发送阻塞的goroutine。

  • qcount 环形队列中的数据个数
  • dataqsize 环形队列的长度
  • buf 指向环形队列的指针
  • sendx 向该通道发送数据的时候,存储在环形队列的什么位置
  • recvx 从该通道接收数据的时候,从环形队列的何处开始读取。
  • elemsize channel中消息的大小
  • elemtype channel中消息的类型
  • recvq 正在读取消息的阻塞协程队列
  • sendq 正在发送消息的阻塞协程队列
  • lock 锁,用于并发保护,比如读写环形队列。
小练习:
  1. 用Go实现一个环形队列(buf所指向的即为环形队列)
  2. 用Go实现一个双向链表(sendq和recvq的类型waiq就是一个双向链表的实现)
参考

channel源代码位置:

本篇就暂时先说到这里,我们对channel就有了个大概的理解,下一篇我们继续讲解channel的初始化,写数据,读数据以及对应的协程唤醒或者阻塞等原理。关注我,不走丢哦