前言:
很无聊,闲来无事看golang一些开源代码,发现有些人作为生产者把消息扔到channel就直接顺手给close掉了,也不等消费者消费完。有些惊奇,这不会丢数据么?按照字面上的意思,我既然close了,呢么消费者应该被唤醒退出。 但经过我的测试发了大量 ch <- data 之后,立马close的话,消费者还是照样会消费数据,不会因为close channel丢消息。
该文章后续仍在不断更新中, 请移步到原文地址 http://xiaorui.cc/?p=5007
那么我们来分析下 channel 为什么不会丢数据 ? 涉及到原理性的话题,不要猜,只能看golang源码了。 golang channel源码里面的hchan结构,close方法及chanrecv方法就可以完美的解释刚才的问题。 channel源码地址, https://golang.org/src/runtime/chan.go,大家可以对照下源码。
Hchan
Hchan是channel的主要数据结构,我们关心的 qcount用来表示消息的个数,closed int32标识用来表示chan的开关,1为关闭.
# xiaorui.cc type hchan struct { qcount uint // total data in the queue ... closed uint32 ...
Closechan
Closechan是chan.go里的关闭channel的方法,该方法除了将 c.closed 设置为 1。还需要唤醒 recvq和sendq 队列里面的阻塞 goroutine. 如果你不唤醒的化,因为没有生产者send数据,也就无法通过事件来唤醒goroutine,所以这时候需要把他们都唤醒起来,判断是否退出的逻辑。
func closechan(c *hchan) { if c == nil { panic(plainError("close of nil channel")) } lock(&c.lock) if c.closed != 0 { unlock(&c.lock) panic(plainError("close of closed channel")) } c.closed = 1 ....
Chanrecv
消费者接收事件相关的逻辑,不是简单的判断closed标志位,而且会判断channel消息个数… 就因为不好好看源码,导致我想让消费者退出的时候,用另外一个exit channel来通信,这样每个消费者用select监听两个channel,一个业务数据,一个通知退出…
玩了几年channel,居然不知道这特性,也是醉了。 这里我给自己解释下,我开发的后端服务基本是那种channel作为队列常住的那样,不会像有些服务动不动就new一个channel,所以….
# xiaorui.cc func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) { ... lock(&c.lock) if c.closed != 0 && c.qcount == 0 { if raceenabled { raceacquire(unsafe.Pointer(c)) } unlock(&c.lock) if ep != nil { typedmemclr(c.elemtype, ep) } return true, false } ...
通过上面的channel源码,我们知道了channel是安全退出的。下面是我测试的脚本,我兴趣的可以跑跑。
// xiaorui.cc package main import ( "fmt" "sync" "time" ) func main() { ch := make(chan int, 2000) var wg sync.WaitGroup go func() { for i := 0; i < 200; i++ { ch <- i } close(ch) }() for i := 0; i < 10; i++ { wg.Add(1) go work(&wg, ch) } wg.Wait() fmt.Println("exit") } func work(wg *sync.WaitGroup, ch chan int) { defer wg.Done() for { time.Sleep(100 * time.Millisecond) select { case data, ok := <- ch: fmt.Println(data, ok) default: fmt.Println("not data") } item, ok := <-ch if !ok { break } } }
总结:
多看源码,多读书 . golang社区里的源码质量大多质量比较高,建议大家多看点源码。 先前看了 golang nsq, boltdb, influxdb的一些源码,感触很多. 当你不知道该学点什么的时候,可以随意在github中闲逛下,总有点项目会让你眼前一亮。