【Go学习】语言基础(四)channel

132 阅读3分钟

Go并发的两大基石:goroutine && channel

谈谈CSP?

CSP,comunicating sequential processes,定义了用于进程通信的输入输出语句(channel的原型),指出应该重视输入输出和并发编程。

channel

作用

  • 在并发编程中,生产者-消费者问题一直是个重点,它就可以实现这两者的解耦,让各自专注于工作。
  • 与之类似的还有对并发数的控制
  • 停止信号
  • 执行定时任务或者超时就结束

性质

image.png

image.png 建通道生成的是一个指针,所以在函数间能直接传递channel本身

发送

image.png

image.png

接收

  • 类型 一种返回是否接收成功的信息;一种没有,只是接收错误时接收者会接收到默认的零值(无法判断是传过来的数据为零值还是因为错误) image.png 两种各有用处。

我个人认为:当不需要确保接收成功(或者说不需要接收的结果的时候可以用第二种)

两种方式都是调用chanrecv():

image.png 第一个bool表示通道是否阻塞/关闭;第二个bool表示是否成功接收到数据(通道中是否存在数据)

对已经关闭的channel进行读写会如何?

能一直读到内容,但是读到的内容根据通道内关闭前是否有元素而不同。
如果 channel 关闭前,buffer 内有元素还未读,会正确读到 chan 内的值,且返回的第二个 bool 值为 true;
如果 chan 关闭前,buffer 内有元素已经被读完,chan 内无值,返回 channel 元素的零值,第二个 bool 值为 false。

写已经关闭的 chan 会 panic。

收发数据的本质?

the copy of value

通道关闭的过程发生了什么

(源码有点长,我直接贴代码+注释一下)


func closechan(c *hchan) {
//如果channel为空,抛出panic
   if c == nil {
      panic(plainError("close of nil channel"))
   }
//加锁
   lock(&c.lock)
   //如果channel已经关闭,抛出panic
   if c.closed != 0 {
      unlock(&c.lock)
      panic(plainError("close of closed channel"))
   }

   if raceenabled {
      callerpc := getcallerpc()
      racewritepc(c.raceaddr(), callerpc, abi.FuncPCABIInternal(closechan))
      racerelease(c.raceaddr())
   }

   c.closed = 1

    //把挂在channel上的sender和receiver全连成一个链表
   var glist gList

   // release all readers
   for {
      sg := c.recvq.dequeue()
      if sg == nil {
         break
      }
      if sg.elem != nil {
         typedmemclr(c.elemtype, sg.elem)
         sg.elem = nil
      }
      if sg.releasetime != 0 {
         sg.releasetime = cputicks()
      }
      gp := sg.g
      gp.param = unsafe.Pointer(sg)
      sg.success = false
      if raceenabled {
         raceacquireg(gp, c.raceaddr())
      }
      glist.push(gp)
   }

   // release all writers (they will panic)
   for {
      sg := c.sendq.dequeue()
      if sg == nil {
         break
      }
      sg.elem = nil
      if sg.releasetime != 0 {
         sg.releasetime = cputicks()
      }
      gp := sg.g
      gp.param = unsafe.Pointer(sg)
      sg.success = false
      if raceenabled {
         raceacquireg(gp, c.raceaddr())
      }
      glist.push(gp)
   }
   //一直到这里才解锁
   unlock(&c.lock)

   // Ready all Gs now that we've dropped the channel lock.
   //唤醒链表中的元素
   for !glist.empty() {
      gp := glist.pop()
      gp.schedlink = 0
      goready(gp, 3)
   }
}

  • 之后,sender检测到channel已关闭,发生panic;
  • receiver进行扫尾工作后返回

正确关闭通道的原则

本质:不要关闭或者发送数据到已关闭的通道
表现:不要关闭有多个并发发送者使用的通道;不要从接收端关闭通道

通道会引发协程资源泄露吗?原因?

会。当有协程操作通道的时候,gc不会回收此类资源,但有可能发生协程在发送或接收的阻塞态,而通道一直满或空的情况。

参考资料

  • Go源码
  • 《Go程序员面试笔试宝典》

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情