go Channel原理 (四)

发布于:2024-07-06 ⋅ 阅读:(120) ⋅ 点赞:(0)

Channel

设计原理

不要通过共享内存的方式进行通信,而是应该通过通信的方式共享内存。

在主流编程语言中,多个线程传递数据的方式一般都是共享内存。
在这里插入图片描述
Go 可以使用共享内存加互斥锁进行通信,同时也提供了一种不同的并发模型,即通信顺序进程(Communicating sequential processes,CSP)。Goroutine 和 Channel 分别对应 CSP 中的实体和传递信息的媒介,Goroutine 之间会通过 Channel 传递数据。在这里插入图片描述
上图中的两个 Goroutine,一个会向 Channel 中发送数据,另一个会从 Channel 中接收数据,它们两者能够独立运行并不存在直接关联,但是能通过 Channel 间接完成通信。

关闭 channel

func closechan(c *hchan) {
   // 关闭一个 nil channel,panic
   if c == nil {
      panic(plainError("close of nil channel"))
   }

   // 上锁
   lock(&c.lock)
   // 如果 channel 已经关闭
   if c.closed != 0 {
      unlock(&c.lock)
      // panic
      panic(plainError("close of closed channel"))
   }

   // …………

   // 修改关闭状态
   c.closed = 1

   var glist *g

   // 将 channel 所有等待接收队列的里 sudog 释放
   for {
      // 从接收队列里出队一个 sudog
      sg := c.recvq.dequeue()
      // 出队完毕,跳出循环
      if sg == nil {
         break
      }

      // 如果 elem 不为空,说明此 receiver 未忽略接收数据
      // 给它赋一个相应类型的零值
      if sg.elem != nil {
         typedmemclr(c.elemtype, sg.elem)
         sg.elem = nil
      }
      if sg.releasetime != 0 {
         sg.releasetime = cputicks()
      }
      // 取出 goroutine
      gp := sg.g
      gp.param = nil
      if raceenabled {
         raceacquireg(gp, unsafe.Pointer(c))
      }
      // 相连,形成链表
      gp.schedlink.set(glist)
      glist = gp
   }

   // 将 channel 等待发送队列里的 sudog 释放
   // 如果存在,这些 goroutine 将会 panic
   for {
      // 从发送队列里出队一个 sudog
      sg := c.sendq.dequeue()
      if sg == nil {
         break
      }

      // 发送者会 panic
      sg.elem = nil
      if sg.releasetime != 0 {
         sg.releasetime = cputicks()
      }
      gp := sg.g
      gp.param = nil
      if raceenabled {
         raceacquireg(gp, unsafe.Pointer(c))
      }
      // 形成链表
      gp.schedlink.set(glist)
      glist = gp
   }
   // 解锁
   unlock(&c.lock)

   // Ready all Gs now that we've dropped the channel lock.
   // 遍历链表
   for glist != nil {
      // 取最后一个
      gp := glist
      // 向前走一步,下一个唤醒的 g
      glist = glist.schedlink.ptr()
      gp.schedlink = 0
      // 唤醒相应 goroutine
      goready(gp, 3)
   }
}
  1. 关闭状态 closed 值为 1。
  2. 将 recvq 和 sendq 中所有正在阻塞的 gorountine 唤醒。sender 直接 panic,receiver 返回一个相应类型的零值。

channel 的使用,有几点不方便的地方:
3. 在不改变 channel 自身状态的情况下,无法获知一个 channel 是否关闭。
4. 关闭一个 closed channel 会导致 panic。
5. 向一个 closed channel 发送数据会导致 panic。

由于关闭一个 cloed 的 channel 或者由 sender 关闭 channel 会导致panic,所以关闭 channel 应该是:
6. channel 不手动关闭由 gc 代劳。
7. 由 sender 关闭并确保只关闭一次(比如 sync.Once)。

channel 发送和接收元素的本质

All transfer of value on the go channels happens with the copy of value.

channel 的发送和接收操作本质上都是值的拷贝。无论是从 sender goroutine 的栈到 chan buf,还是从 chan buf 到 receiver goroutine,或者是直接从 sender goroutine 到 receiver goroutine。

channel 在什么情况下会引起资源泄漏

channel 可能会引发 goroutine 泄漏。
泄漏的原因是 goroutine 操作 channel 后,处于发送或接收阻塞状态,而 channel 处于满或空的状态,一直得不到改变。同时,垃圾回收器也不会回收此类资源,进而导致 gouroutine 会一直处于等待队列中。
程序运行过程中,对于一个 channel,如果没有任何 goroutine 引用了,gc 会对其进行回收操作,不会引起内存泄漏。


网站公告

今日签到

点亮在社区的每一天
去签到