原文:https://www.jianshu.com/p/24ede9e90490

----------------------------------

由浅入深剖析 go channel

channel 是 golang 中最核心的 feature 之一,因此理解 Channel 的原理对于学习和使用 golang 非常重要。

channel 是 goroutine 之间通信的一种方式,可以类比成 Unix 中的进程的通信方式管道。

CSP 模型

在讲 channel 之前,有必要先提一下 CSP 模型,传统的并发模型主要分为 Actor 模型和 CSP 模型,CSP 模型全称为 communicating sequential processes,CSP 模型由并发执行实体(进程,线程或协程),和消息通道组成,实体之间通过消息通道发送消息进行通信。和 Actor 模型不同,CSP 模型关注的是消息发送的载体,即通道,而不是发送消息的执行实体。关于 CSP 模型的更进一步的介绍,有兴趣的同学可以阅读论文 Communicating Sequential Processes,Go 语言的并发模型参考了 CSP 理论,其中执行实体对应的是 goroutine, 消息通道对应的就是 channel。

channel 介绍

channel 提供了一种通信机制,通过它,一个 goroutine 可以想另一 goroutine 发送消息。channel 本身还需关联了一个类型,也就是 channel 可以发送数据的类型。例如: 发送 int 类型消息的 channel 写作 chan int 。

channel 创建

channel 使用内置的 make 函数创建,下面声明了一个 chan int 类型的 channel:

  1. ch := make(chan int)

c和 map 类似,make 创建了一个底层数据结构的引用,当赋值或参数传递时,只是拷贝了一个 channel 引用,指向相同的 channel 对象。和其他引用类型一样,channel 的空值为 nil 。使用 == 可以对类型相同的 channel 进行比较,只有指向相同对象或同为 nil 时,才返回 true

channel 的读写操作

  1. ch := make(chan int)
  2. // write to channel
  3. ch <- x
  4. // read from channel
  5. x <- ch
  6. // another way to read
  7. x = <- ch

channel 一定要初始化后才能进行读写操作,否则会永久阻塞。

关闭 channel

golang 提供了内置的 close 函数对 channel 进行关闭操作。

  1. ch := make(chan int)
  2. close(ch)

有关 channel 的关闭,你需要注意以下事项:

  • 关闭一个未初始化(nil) 的 channel 会产生 panic
  • 重复关闭同一个 channel 会产生 panic
  • 向一个已关闭的 channel 中发送消息会产生 panic
  • 从已关闭的 channel 读取消息不会产生 panic,且能读出 channel 中还未被读取的消息,若消息均已读出,则会读到类型的零值。从一个已关闭的 channel 中读取消息永远不会阻塞,并且会返回一个为 false 的 ok-idiom,可以用它来判断 channel 是否关闭
  • 关闭 channel 会产生一个广播机制,所有向 channel 读取消息的 goroutine 都会收到消息
  1. ch := make(chan int, 10)
  2. ch <- 11
  3. ch <- 12
  4. close(ch)
  5. for x := range ch {
  6. fmt.Println(x)
  7. }
  8. x, ok := <- ch
  9. fmt.Println(x, ok)
  10. -----
  11. output:
  12. 11
  13. 12
  14. 0 false

channel 的类型

channel 分为不带缓存的 channel 和带缓存的 channel。

无缓存的 channel

从无缓存的 channel 中读取消息会阻塞,直到有 goroutine 向该 channel 中发送消息;同理,向无缓存的 channel 中发送消息也会阻塞,直到有 goroutine 从 channel 中读取消息。

通过无缓存的 channel 进行通信时,接收者收到数据 happens before 发送者 goroutine 唤醒

有缓存的 channel

有缓存的 channel 的声明方式为指定 make 函数的第二个参数,该参数为 channel 缓存的容量

  1. ch := make(chan int, 10)

有缓存的 channel 类似一个阻塞队列(采用环形数组实现)。当缓存未满时,向 channel 中发送消息时不会阻塞,当缓存满时,发送操作将被阻塞,直到有其他 goroutine 从中读取消息;相应的,当 channel 中消息不为空时,读取消息不会出现阻塞,当 channel 为空时,读取操作会造成阻塞,直到有 goroutine 向 channel 中写入消息。

  1. ch := make(chan int, 3)
  2. // blocked, read from empty buffered channel
  3. <- ch
  1. ch := make(chan int, 3)
  2. ch <- 1
  3. ch <- 2
  4. ch <- 3
  5. // blocked, send to full buffered channel
  6. ch <- 4

通过 len 函数可以获得 chan 中的元素个数,通过 cap 函数可以得到 channel 的缓存长度。

channel 的用法

goroutine 通信

看一个 effective go 中的例子:

  1. c := make(chan int) // Allocate a channel.
  2. // Start the sort in a goroutine; when it completes, signal on the channel.
  3. go func() {
  4. list.Sort()
  5. c <- 1 // Send a signal; value does not matter.
  6. }()
  7. doSomethingForAWhile()
  8. <-c

主 goroutine 会阻塞,直到执行 sort 的 goroutine 完成。

range 遍历

channel 也可以使用 range 取值,并且会一直从 channel 中读取数据,直到有 goroutine 对改 channel 执行 close 操作,循环才会结束。

  1. // consumer worker
  2. ch := make(chan int, 10)
  3. for x := range ch{
  4. fmt.Println(x)
  5. }

等价于

  1. for {
  2. x, ok := <- ch
  3. if !ok {
  4. break
  5. }
  6. fmt.Println(x)
  7. }

配合 select 使用

select 用法类似与 IO 多路复用,可以同时监听多个 channel 的消息状态,看下面的例子

  1. select {
  2. case <- ch1:
  3. ...
  4. case <- ch2:
  5. ...
  6. case ch3 <- 10;
  7. ...
  8. default:
  9. ...
  10. }
  • select 可以同时监听多个 channel 的写入或读取
  • 执行 select 时,若只有一个 case 通过(不阻塞),则执行这个 case 块
  • 若有多个 case 通过,则随机挑选一个 case 执行
  • 若所有 case 均阻塞,且定义了 default 模块,则执行 default 模块。若未定义 default 模块,则 select 语句阻塞,直到有 case 被唤醒。
  • 使用 break 会跳出 select 块。

1. 设置超时时间

  1. ch := make(chan struct{})
  2. // finish task while send msg to ch
  3. go doTask(ch)
  4. timeout := time.After(5 * time.Second)
  5. select {
  6. case <- ch:
  7. fmt.Println("task finished.")
  8. case <- timeout:
  9. fmt.Println("task timeout.")
  10. }

2. quite channel

有一些场景中,一些 worker goroutine 需要一直循环处理信息,直到收到 quit 信号

  1. msgCh := make(chan struct{})
  2. quitCh := make(chan struct{})
  3. for {
  4. select {
  5. case <- msgCh:
  6. doWork()
  7. case <- quitCh:
  8. finish()
  9. return
  10. }

单向 channel

即只可写入或只可读的channel,事实上 channel 只读或只写都没有意义,所谓的单向 channel 其实知识声明时用,比如

  1. func foo(ch chan<- int) <-chan int {...}

chan<- int 表示一个只可写入的 channel,<-chan int 表示一个只可读取的 channel。上面这个函数约定了 foo 内只能从向 ch 中写入数据,返回只一个只能读取的 channel,虽然使用普通的 channel 也没有问题,但这样在方法声明时约定可以防止 channel 被滥用,这种预防机制发生再编译期间。

channel 源码分析

channel 的主要实现在 src/runtime/chan.go 中,以下源码均基于 go1.9.2。源码阅读时为了更好的理解 channel 特性,帮助正确合理的使用 channel,阅读代码的过程可以回忆前面章节的 channel 特性。

channel 类结构

channel 相关类定义如下:

  1. // channel 类型定义
  2. type hchan struct {
  3. // channel 中的元素数量, len
  4. qcount uint // total data in the queue
  5. // channel 的大小, cap
  6. dataqsiz uint // size of the circular queue
  7. // channel 的缓冲区,环形数组实现
  8. buf unsafe.Pointer // points to an array of dataqsiz elements
  9. // 单个元素的大小
  10. elemsize uint16
  11. // closed 标志位
  12. closed uint32
  13. // 元素的类型
  14. elemtype *_type // element type
  15. // send 和 recieve 的索引,用于实现环形数组队列
  16. sendx uint // send index
  17. recvx uint // receive index
  18. // recv goroutine 等待队列
  19. recvq waitq // list of recv waiters
  20. // send goroutine 等待队列
  21. sendq waitq // list of send waiters
  22. // lock protects all fields in hchan, as well as several
  23. // fields in sudogs blocked on this channel.
  24. //
  25. // Do not change another G's status while holding this lock
  26. // (in particular, do not ready a G), as this can deadlock
  27. // with stack shrinking.
  28. lock mutex
  29. }
  30. // 等待队列的链表实现
  31. type waitq struct {
  32. first *sudog
  33. last *sudog
  34. }
  35. // in src/runtime/runtime2.go
  36. // 对 G 的封装
  37. type sudog struct {
  38. // The following fields are protected by the hchan.lock of the
  39. // channel this sudog is blocking on. shrinkstack depends on
  40. // this for sudogs involved in channel ops.
  41. g *g
  42. selectdone *uint32 // CAS to 1 to win select race (may point to stack)
  43. next *sudog
  44. prev *sudog
  45. elem unsafe.Pointer // data element (may point to stack)
  46. // The following fields are never accessed concurrently.
  47. // For channels, waitlink is only accessed by g.
  48. // For semaphores, all fields (including the ones above)
  49. // are only accessed when holding a semaRoot lock.
  50. acquiretime int64
  51. releasetime int64
  52. ticket uint32
  53. parent *sudog // semaRoot binary tree
  54. waitlink *sudog // g.waiting list or semaRoot
  55. waittail *sudog // semaRoot
  56. c *hchan // channel
  57. }

可以看到,channel 的主要组成有:一个环形数组实现的队列,用于存储消息元素;两个链表实现的 goroutine 等待队列,用于存储阻塞在 recv 和 send 操作上的 goroutine;一个互斥锁,用于各个属性变动的同步

channel make 实现

  1. func makechan(t *chantype, size int64) *hchan {
  2. elem := t.elem
  3. // compiler checks this but be safe.
  4. if elem.size >= 1<<16 {
  5. throw("makechan: invalid channel element type")
  6. }
  7. if hchanSize%maxAlign != 0 || elem.align > maxAlign {
  8. throw("makechan: bad alignment")
  9. }
  10. if size < 0 || int64(uintptr(size)) != size || (elem.size > 0 && uintptr(size) > (_MaxMem-hchanSize)/elem.size) {
  11. panic(plainError("makechan: size out of range"))
  12. }
  13. var c *hchan
  14. if elem.kind&kindNoPointers != 0 || size == 0 {
  15. // case 1: channel 不含有指针
  16. // case 2: size == 0,即无缓冲 channel
  17. // Allocate memory in one call.
  18. // Hchan does not contain pointers interesting for GC in this case:
  19. // buf points into the same allocation, elemtype is persistent.
  20. // SudoG's are referenced from their owning thread so they can't be collected.
  21. // TODO(dvyukov,rlh): Rethink when collector can move allocated objects.
  22. // 在堆上分配连续的空间用作 channel
  23. c = (*hchan)(mallocgc(hchanSize+uintptr(size)*elem.size, nil, true))
  24. if size > 0 && elem.size != 0 {
  25. c.buf = add(unsafe.Pointer(c), hchanSize)
  26. } else {
  27. // race detector uses this location for synchronization
  28. // Also prevents us from pointing beyond the allocation (see issue 9401).
  29. c.buf = unsafe.Pointer(c)
  30. }
  31. } else {
  32. // 有缓冲 channel 初始化
  33. c = new(hchan)
  34. // 堆上分配 buf 内存
  35. c.buf = newarray(elem, int(size))
  36. }
  37. c.elemsize = uint16(elem.size)
  38. c.elemtype = elem
  39. c.dataqsiz = uint(size)
  40. if debugChan {
  41. print("makechan: chan=", c, "; elemsize=", elem.size, "; elemalg=", elem.alg, "; dataqsiz=", size, "\n")
  42. }
  43. return c
  44. }

make 的过程还比较简单,需要注意一点的是当元素不含指针的时候,会将整个 hchan 分配成一个连续的空间。

channel send

  1. // entry point for c <- x from compiled code
  2. //go:nosplit
  3. func chansend1(c *hchan, elem unsafe.Pointer) {
  4. chansend(c, elem, true, getcallerpc(unsafe.Pointer(&c)))
  5. }
  6. /*
  7. * generic single channel send/recv
  8. * If block is not nil,
  9. * then the protocol will not
  10. * sleep but return if it could
  11. * not complete.
  12. *
  13. * sleep can wake up with g.param == nil
  14. * when a channel involved in the sleep has
  15. * been closed. it is easiest to loop and re-run
  16. * the operation; we'll see that it's now closed.
  17. */
  18. func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
  19. // 前面章节说道的,当 channel 未初始化或为 nil 时,向其中发送数据将会永久阻塞
  20. if c == nil {
  21. if !block {
  22. return false
  23. }
  24. // gopark 会使当前 goroutine 休眠,并通过 unlockf 唤醒,但是此时传入的 unlockf 为 nil, 因此,goroutine 会一直休眠
  25. gopark(nil, nil, "chan send (nil chan)", traceEvGoStop, 2)
  26. throw("unreachable")
  27. }
  28. if debugChan {
  29. print("chansend: chan=", c, "\n")
  30. }
  31. if raceenabled {
  32. racereadpc(unsafe.Pointer(c), callerpc, funcPC(chansend))
  33. }
  34. // Fast path: check for failed non-blocking operation without acquiring the lock.
  35. //
  36. // After observing that the channel is not closed, we observe that the channel is
  37. // not ready for sending. Each of these observations is a single word-sized read
  38. // (first c.closed and second c.recvq.first or c.qcount depending on kind of channel).
  39. // Because a closed channel cannot transition from 'ready for sending' to
  40. // 'not ready for sending', even if the channel is closed between the two observations,
  41. // they imply a moment between the two when the channel was both not yet closed
  42. // and not ready for sending. We behave as if we observed the channel at that moment,
  43. // and report that the send cannot proceed.
  44. //
  45. // It is okay if the reads are reordered here: if we observe that the channel is not
  46. // ready for sending and then observe that it is not closed, that implies that the
  47. // channel wasn't closed during the first observation.
  48. if !block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) ||
  49. (c.dataqsiz > 0 && c.qcount == c.dataqsiz)) {
  50. return false
  51. }
  52. var t0 int64
  53. if blockprofilerate > 0 {
  54. t0 = cputicks()
  55. }
  56. // 获取同步锁
  57. lock(&c.lock)
  58. // 之前章节提过,向已经关闭的 channel 发送消息会产生 panic
  59. if c.closed != 0 {
  60. unlock(&c.lock)
  61. panic(plainError("send on closed channel"))
  62. }
  63. // CASE1: 当有 goroutine 在 recv 队列上等待时,跳过缓存队列,将消息直接发给 reciever goroutine
  64. if sg := c.recvq.dequeue(); sg != nil {
  65. // Found a waiting receiver. We pass the value we want to send
  66. // directly to the receiver, bypassing the channel buffer (if any).
  67. send(c, sg, ep, func() { unlock(&c.lock) }, 3)
  68. return true
  69. }
  70. // CASE2: 缓存队列未满,则将消息复制到缓存队列上
  71. if c.qcount < c.dataqsiz {
  72. // Space is available in the channel buffer. Enqueue the element to send.
  73. qp := chanbuf(c, c.sendx)
  74. if raceenabled {
  75. raceacquire(qp)
  76. racerelease(qp)
  77. }
  78. typedmemmove(c.elemtype, qp, ep)
  79. c.sendx++
  80. if c.sendx == c.dataqsiz {
  81. c.sendx = 0
  82. }
  83. c.qcount++
  84. unlock(&c.lock)
  85. return true
  86. }
  87. if !block {
  88. unlock(&c.lock)
  89. return false
  90. }
  91. // CASE3: 缓存队列已满,将goroutine 加入 send 队列
  92. // 初始化 sudog
  93. // Block on the channel. Some receiver will complete our operation for us.
  94. gp := getg()
  95. mysg := acquireSudog()
  96. mysg.releasetime = 0
  97. if t0 != 0 {
  98. mysg.releasetime = -1
  99. }
  100. // No stack splits between assigning elem and enqueuing mysg
  101. // on gp.waiting where copystack can find it.
  102. mysg.elem = ep
  103. mysg.waitlink = nil
  104. mysg.g = gp
  105. mysg.selectdone = nil
  106. mysg.c = c
  107. gp.waiting = mysg
  108. gp.param = nil
  109. // 加入队列
  110. c.sendq.enqueue(mysg)
  111. // 休眠
  112. goparkunlock(&c.lock, "chan send", traceEvGoBlockSend, 3)
  113. // 唤醒 goroutine
  114. // someone woke us up.
  115. if mysg != gp.waiting {
  116. throw("G waiting list is corrupted")
  117. }
  118. gp.waiting = nil
  119. if gp.param == nil {
  120. if c.closed == 0 {
  121. throw("chansend: spurious wakeup")
  122. }
  123. panic(plainError("send on closed channel"))
  124. }
  125. gp.param = nil
  126. if mysg.releasetime > 0 {
  127. blockevent(mysg.releasetime-t0, 2)
  128. }
  129. mysg.c = nil
  130. releaseSudog(mysg)
  131. return true
  132. }

从 send 代码中可以看到,之前章节提到的一些特性都在代码中有所体现,

send 有以下几种情况:

  • 有 goroutine 阻塞在 channel recv 队列上,此时缓存队列为空,直接将消息发送给 reciever goroutine,只产生一次复制
  • 当 channel 缓存队列有剩余空间时,将数据放到队列里,等待接收,接收后总共产生两次复制
  • 当 channel 缓存队列已满时,将当前 goroutine 加入 send 队列并阻塞。

channel recieve

  1. // entry points for <- c from compiled code
  2. //go:nosplit
  3. func chanrecv1(c *hchan, elem unsafe.Pointer) {
  4. chanrecv(c, elem, true)
  5. }
  6. //go:nosplit
  7. func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) {
  8. _, received = chanrecv(c, elem, true)
  9. return
  10. }
  11. // chanrecv receives on channel c and writes the received data to ep.
  12. // ep may be nil, in which case received data is ignored.
  13. // If block == false and no elements are available, returns (false, false).
  14. // Otherwise, if c is closed, zeros *ep and returns (true, false).
  15. // Otherwise, fills in *ep with an element and returns (true, true).
  16. // A non-nil ep must point to the heap or the caller's stack.
  17. func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
  18. // raceenabled: don't need to check ep, as it is always on the stack
  19. // or is new memory allocated by reflect.
  20. if debugChan {
  21. print("chanrecv: chan=", c, "\n")
  22. }
  23. // 从 nil 的 channel 中接收消息,永久阻塞
  24. if c == nil {
  25. if !block {
  26. return
  27. }
  28. gopark(nil, nil, "chan receive (nil chan)", traceEvGoStop, 2)
  29. throw("unreachable")
  30. }
  31. // Fast path: check for failed non-blocking operation without acquiring the lock.
  32. //
  33. // After observing that the channel is not ready for receiving, we observe that the
  34. // channel is not closed. Each of these observations is a single word-sized read
  35. // (first c.sendq.first or c.qcount, and second c.closed).
  36. // Because a channel cannot be reopened, the later observation of the channel
  37. // being not closed implies that it was also not closed at the moment of the
  38. // first observation. We behave as if we observed the channel at that moment
  39. // and report that the receive cannot proceed.
  40. //
  41. // The order of operations is important here: reversing the operations can lead to
  42. // incorrect behavior when racing with a close.
  43. if !block && (c.dataqsiz == 0 && c.sendq.first == nil ||
  44. c.dataqsiz > 0 && atomic.Loaduint(&c.qcount) == 0) &&
  45. atomic.Load(&c.closed) == 0 {
  46. return
  47. }
  48. var t0 int64
  49. if blockprofilerate > 0 {
  50. t0 = cputicks()
  51. }
  52. lock(&c.lock)
  53. // CASE1: 从已经 close 且为空的 channel recv 数据,返回空值
  54. if c.closed != 0 && c.qcount == 0 {
  55. if raceenabled {
  56. raceacquire(unsafe.Pointer(c))
  57. }
  58. unlock(&c.lock)
  59. if ep != nil {
  60. typedmemclr(c.elemtype, ep)
  61. }
  62. return true, false
  63. }
  64. // CASE2: send 队列不为空
  65. // CASE2.1: 缓存队列为空,直接从 sender recv 元素
  66. // CASE2.2: 缓存队列不为空,此时只有可能是缓存队列已满,从队列头取出元素,并唤醒 sender 将元素写入缓存队列尾部。由于为环形队列,因此,队列满时只需要将队列头复制给 reciever,同时将 sender 元素复制到该位置,并移动队列头尾索引,不需要移动队列元素
  67. if sg := c.sendq.dequeue(); sg != nil {
  68. // Found a waiting sender. If buffer is size 0, receive value
  69. // directly from sender. Otherwise, receive from head of queue
  70. // and add sender's value to the tail of the queue (both map to
  71. // the same buffer slot because the queue is full).
  72. recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
  73. return true, true
  74. }
  75. // CASE3: 缓存队列不为空,直接从队列取元素,移动头索引
  76. if c.qcount > 0 {
  77. // Receive directly from queue
  78. qp := chanbuf(c, c.recvx)
  79. if raceenabled {
  80. raceacquire(qp)
  81. racerelease(qp)
  82. }
  83. if ep != nil {
  84. typedmemmove(c.elemtype, ep, qp)
  85. }
  86. typedmemclr(c.elemtype, qp)
  87. c.recvx++
  88. if c.recvx == c.dataqsiz {
  89. c.recvx = 0
  90. }
  91. c.qcount--
  92. unlock(&c.lock)
  93. return true, true
  94. }
  95. if !block {
  96. unlock(&c.lock)
  97. return false, false
  98. }
  99. // CASE4: 缓存队列为空,将 goroutine 加入 recv 队列,并阻塞
  100. // no sender available: block on this channel.
  101. gp := getg()
  102. mysg := acquireSudog()
  103. mysg.releasetime = 0
  104. if t0 != 0 {
  105. mysg.releasetime = -1
  106. }
  107. // No stack splits between assigning elem and enqueuing mysg
  108. // on gp.waiting where copystack can find it.
  109. mysg.elem = ep
  110. mysg.waitlink = nil
  111. gp.waiting = mysg
  112. mysg.g = gp
  113. mysg.selectdone = nil
  114. mysg.c = c
  115. gp.param = nil
  116. c.recvq.enqueue(mysg)
  117. goparkunlock(&c.lock, "chan receive", traceEvGoBlockRecv, 3)
  118. // someone woke us up
  119. if mysg != gp.waiting {
  120. throw("G waiting list is corrupted")
  121. }
  122. gp.waiting = nil
  123. if mysg.releasetime > 0 {
  124. blockevent(mysg.releasetime-t0, 2)
  125. }
  126. closed := gp.param == nil
  127. gp.param = nil
  128. mysg.c = nil
  129. releaseSudog(mysg)
  130. return true, !closed
  131. }

channel close

  1. func closechan(c *hchan) {
  2. if c == nil {
  3. panic(plainError("close of nil channel"))
  4. }
  5. lock(&c.lock)
  6. // 重复 close,产生 panic
  7. if c.closed != 0 {
  8. unlock(&c.lock)
  9. panic(plainError("close of closed channel"))
  10. }
  11. if raceenabled {
  12. callerpc := getcallerpc(unsafe.Pointer(&c))
  13. racewritepc(unsafe.Pointer(c), callerpc, funcPC(closechan))
  14. racerelease(unsafe.Pointer(c))
  15. }
  16. c.closed = 1
  17. var glist *g
  18. // 唤醒所有 reciever
  19. // release all readers
  20. for {
  21. sg := c.recvq.dequeue()
  22. if sg == nil {
  23. break
  24. }
  25. if sg.elem != nil {
  26. typedmemclr(c.elemtype, sg.elem)
  27. sg.elem = nil
  28. }
  29. if sg.releasetime != 0 {
  30. sg.releasetime = cputicks()
  31. }
  32. gp := sg.g
  33. gp.param = nil
  34. if raceenabled {
  35. raceacquireg(gp, unsafe.Pointer(c))
  36. }
  37. gp.schedlink.set(glist)
  38. glist = gp
  39. }
  40. // 唤醒所有 sender,并产生 panic
  41. // release all writers (they will panic)
  42. for {
  43. sg := c.sendq.dequeue()
  44. if sg == nil {
  45. break
  46. }
  47. sg.elem = nil
  48. if sg.releasetime != 0 {
  49. sg.releasetime = cputicks()
  50. }
  51. gp := sg.g
  52. gp.param = nil
  53. if raceenabled {
  54. raceacquireg(gp, unsafe.Pointer(c))
  55. }
  56. gp.schedlink.set(glist)
  57. glist = gp
  58. }
  59. unlock(&c.lock)
  60. // Ready all Gs now that we've dropped the channel lock.
  61. for glist != nil {
  62. gp := glist
  63. glist = glist.schedlink.ptr()
  64. gp.schedlink = 0
  65. goready(gp, 3)
  66. }
  67. }

小礼物走一走,来简书关注我

作者:不智鱼
链接:https://www.jianshu.com/p/24ede9e90490
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

由浅入深剖析 go channel的更多相关文章

  1. 由浅入深剖析.htaccess

    转自:http://blog.csdn.net/21aspnet/article/details/6908025 [-] htaccess文件使用前提 htaccess基本语法介绍 现学现用学习正则表 ...

  2. Apache重写规则由浅入深剖析.htaccess

    1..htaccess文件使用前提 .htaccess的主要作用就是实现url改写,也就是当浏览器通过url访问到服务器某个文件夹时,作为主人,我们可以来接待这个url,具体 地怎样接待它,就是此文件 ...

  3. 服务器.htaccess 详解以及 .htaccess 参数说明(转载)

    htaccess文件(或者”分布式配置文件”)提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录.作为用户,所能使用的命令受到限 ...

  4. .htaccess详解及.htaccess参数说明【转】

    目录(?)[-] htaccess 详解 htaccess rewrite 规则详细说明 RewriteEngine OnOff RewriteBase URL-path RewriteCond Te ...

  5. Hadoop 2.x从零基础到挑战百万年薪第一季

    鉴于目前大数据Hadoop 2.x被企业广泛使用,在实际的企业项目中需要更加深入的灵活运用,并且Hadoop 2.x是大数据平台处理 的框架的基石,尤其在海量数据的存储HDFS.分布式资源管理和任务调 ...

  6. 工程管理之makefile与自动创建makefile文件过程

    (风雪之隅 http://www.laruence.com/2009/11/18/1154.html) Linux Makefile自动编译和链接使用的环境 想知道到Linux Makefile系统的 ...

  7. 【转】.htaccess详解及.htaccess参数说明

    .htaccess文件(或者”分布式配置文件”)提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录.作为用户,所能使用的命令受到 ...

  8. Netty源码分析第3章(客户端接入流程)---->第1节: 初始化NioSockectChannelConfig

    Netty源码分析第三章: 客户端接入流程 概述: 之前的章节学习了server启动以及eventLoop相关的逻辑, eventLoop轮询到客户端接入事件之后是如何处理的?这一章我们循序渐进, 带 ...

  9. .htaccess详解及.htaccess参数说明

    .htaccess文件(或者”分布式配置文件”)提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录.作为用户,所能使用的命令受到 ...

随机推荐

  1. Red Hat Enterprise 6.5 在虚拟机上将系统语言修改为中文

    Red Hat Enterprise 6.5 在虚拟机上将系统语言修改为中文 说明:本文是个人在使用RedHat时候为方便而设置的,作为学习札记记录. 在虚拟机安装RedHat时候会跳过语言的安装选项 ...

  2. 如何保持人际关系zz

    作者:Faaany链接:https://www.zhihu.com/question/35590289/answer/63675007来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载 ...

  3. [转帖]拿小本本记下的Linux Shell常用技巧(一)

    拿小本本记下的Linux Shell常用技巧(一) https://zhuanlan.zhihu.com/p/73361101 一. 特殊文件: /dev/null和/dev/tty Linux系统提 ...

  4. redis事务、并发及应用场景

    目录 事务概念 事务命令 乐观锁 悲观锁 并发控制及过期时间 队列 队列防丢失 阻塞队列 时间区间控制 持久化 RDB AOF 命令追加 文件写入.同步 RDB.AOF优缺点 RDB优缺 AOF优缺 ...

  5. php使用ffmpeg获取上传的视频的时长,码率等信息

    视频上传是程序员在很多时候需要用到的操作,然而上传完视频肯定要获得一些视频的详细信息,php本身是不支持信息获取的 ,所以采用ffmpeg第三方插件 首先你需要下载ffmpeg文件:官网地址:http ...

  6. 新浪sae对storage的文档进行读写操作

    有的人喜欢将一些数据写在服务器的文件里面,并不喜欢存在mysql里,但新浪sae却不支持对本地文件进行操作. 不过sae拓展了一个storage的服务,可以将一些静态文件放在上面.本文不介绍文件的上传 ...

  7. mysql中char和varchar区别

    char是一种固定长度的类型,varchar则是一种可变长度的类型  char(M)类型的数据列里,每个值都占用M个字节,如果某个长度小于M,MySQL就会在它的右边用空格字符补足.(在检索操作中那些 ...

  8. 03 HttpServletRequest_HttpServletResponse

    HttpServletRequest:一次来自客户端的请求的相关信息 请求行 request.getMethod() 获取http请求方式 request.getRequestURI() 获取统一资源 ...

  9. Admui相关第三方插件

    ace 版本:1.2.3au 官网:https://github.com/ajaxorg/ace-builds/ 许可:BSD 依赖:无 DataAPI:data-pulgin="ace&q ...

  10. This is very likely to create a memory leak. Stack trace of thread错误分析

    1.问题描述 启动tomcat部署项目时,报This is very likely to create a memory leak. Stack trace of thread错误. 29-May-2 ...