go-channel使用

channel的使用

qcount:代表 chan 中已经接收但还没被取走的元素的个数。内建函数 len 可以返回这个字段的值。
dataqsiz:队列的大小。chan 使用一个循环队列来存放元素,循环队列很适合这种生产者 - 消费者的场景(我很好奇为什么这个字段省略 size 中的 e)。
buf:存放元素的循环队列的 buffer。
elemtype 和 elemsize:chan 中元素的类型和 size。因为 chan 一旦声明,它的元素类型是固定的,即普通类型或者指针类型,所以元素大小也是固定的。
sendx:处理发送数据的指针在 buf 中的位置。一旦接收了新的数据,指针就会加上 elemsize,移向下一个位置。buf 的总大小是 elemsize 的整数倍,而且 buf 是一个循环列表。
recvx:处理接收请求时的指针在 buf 中的位置。一旦取出数据,此指针会移动到下一个位置。
recvq:chan 是多生产者多消费者的模式,如果消费者因为没有数据可读而被阻塞了,就会被加入到 recvq 队列中。
sendq:如果生产者因为 buf 满了而阻塞,会被加入到 sendq 队列中
lock mutex: 互斥锁,保护所有字段

初始化

func makechan(t *chantype, size int) *hchan {
elem := t.elem

    // 略去检查代码
    mem, overflow := math.MulUintptr(elem.size, uintptr(size))
    
//
var c *hchan
switch {
case mem == 0:
  // chan的size或者元素的size是0,不必创建buf
  c = (*hchan)(mallocgc(hchanSize, nil, true))
  c.buf = c.raceaddr()
case elem.ptrdata == 0:
  // 元素不是指针,分配一块连续的内存给hchan数据结构和buf
  c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
        // hchan数据结构后面紧接着就是buf
  c.buf = add(unsafe.Pointer(c), hchanSize)
default:
  // 元素包含指针,那么单独分配buf
  c = new(hchan)
  c.buf = mallocgc(mem, elem, true)
}

    // 元素大小、类型、容量都记录下来
c.elemsize = uint16(elem.size)
c.elemtype = elem
c.dataqsiz = uint(size)
lockInit(&c.lock, lockRankHchan)

return c

}

主要错误

首先,我们来总结下会 panic 的情况,总共有 3 种:

  1. close 为 nil 的 chan;
  2. send 已经 close 的 chan;
  3. close 已经 close 的 chan。

还有go routine泄露的问题,即go routine一直阻塞,永远结束不了。
如下例子中,使用unbuffer的chan,在没有receiver时,发送者一直在阻塞

  func process(timeout time.Duration) bool {
    ch := make(chan bool)

    go func() {
        // 模拟处理耗时的业务
        time.Sleep((timeout + time.Second))
        ch <- true // block
        fmt.Println("exit goroutine")
    }()
    select {
    case result := <-ch:
        return result
    case <-time.After(timeout):
        return false
    }
}

channel 并不是解决并发问题的“银弹”,有时候使用并发原语更简单,且不容易出错。
那么原则上是:

  • 共享资源的并发访问使用传统并发原语
  • 复杂的任务编排和消息传递使用channel
  • 消息通知使用channel,除非只想signal一个goroutine,才使用cond
  • 简单等待所有任务完成用WaitGroup,也有 Channel 的推崇者用 Channel,都可以;
  • 需要和Select语句结合,使用channel
  • 需要和超时配合时,使用channel和Context
上一篇:Golang 双向, 单向 channel 爬取 股票信息 chan <-, 解析 <-chan


下一篇:go的chan