Go渠道是如何实施的?

Mat*_*att 62 channel go

在(简要)回顾Go语言规范,有效Go和Go内存模型后,我仍然不清楚Go渠道如何在幕后工作.

他们是什么样的结构?它们的行为类似于线程安全的队列/数组.

他们的实现是否依赖于架构?

mna*_*mna 73

通道的源文件是(来自你的源代码根目录)/src/pkg/runtime/chan.go.

hchan是通道的中央数据结构,具有发送和接收链接列表(保存指向其goroutine和数据元素的指针)和closed标志.Lock在runtime2.go中定义了一个嵌入式结构,它可以作为互斥锁(futex)或信号量,具体取决于操作系统.锁定实现位于lock_futex.go(Linux/Dragonfly/Some BSD)或lock_sema.go(Windows/OSX/Plan9/Some BSD)中,基于构建标记.

通道操作都在这个chan.go文件中实现,因此你可以看到makechan,send和receive操作,以及select构造,close,len和cap内置函数.

有关频道内部运作的深入解释,你必须阅读Dmitry Vyukov自己的Go 类固醇通道(Go core dev,goroutines,scheduler and channels等).


Net*_*Kat 11

你问了两个问题:

  1. 它们是什么样的结构?

Go 中的 Channel 确实“有点像线程安全的队列”,更准确地说,Go 中的 Channel 具有以下属性:

  • Goroutine 安全的
  • 提供 FIFO 语义
  • 可以在 goroutine 之间存储和传递值
  • 导致 goroutine 阻塞和解除阻塞

每次创建通道时,都会在堆上分配一个hchan结构,并返回一个指向 hchan 内存位置的指针,表示为通道,这就是 go 例程共享它的方式。

上述前两个属性的实现方式与带锁的队列类似。通道可以传递给不同 go 例程的元素被实现为循环队列(环形缓冲区),其索引位于 hchan 结构体中,索引说明元素在缓冲区中的位置。

循环队列:

qcount   uint           // total data in the queue
dataqsiz uint           // size of the circular queue
buf      unsafe.Pointer // points to an array of dataqsiz elements
Run Code Online (Sandbox Code Playgroud)

和指数:

sendx    uint   // send index
recvx    uint   // receive index
Run Code Online (Sandbox Code Playgroud)

每次 go 例程需要访问通道结构并修改其状态时,它都会持有锁,例如:将元素复制到缓冲区或从缓冲区复制元素、更新列表或索引。某些操作被优化为无锁,但这超出了本答案的范围。

go 通道的阻塞和非阻塞属性是通过使用两个保存阻塞 go 例程的队列(链表)来实现的

recvq    waitq  // list of recv waiters
sendq    waitq  // list of send waiters
Run Code Online (Sandbox Code Playgroud)

每次 go-routine 想要将任务添加到满通道(缓冲区已满),或者从空通道(缓冲区为空)获取任务时,都会分配一个伪 go-routine sudog结构,并且 go-routine将 sudog 作为节点相应地添加到发送或接收等待者列表中。然后,go 例程使用特殊调用更新 go 运行时调度程序,这会提示何时应将它们退出执行 ( gopark) 或准备运行 ( goready)。请注意,这是一个非常简单的解释,隐藏了一些复杂性。

  1. 它们的实现取决于架构吗?

除了@mna已经解释过的特定于操作系统的锁实现之外,我不知道任何体系结构特定的约束优化或差异。


ayk*_*yke 5

这是一个很好的演讲,大致描述了渠道的实现方式:https
//youtu.be/KBZlN0izeiY

谈话说明:

GopherCon 2017:Kavya Joshi-了解频道

通道为goroutines提供了一种简单的通信机制,并为构建复杂的并发模式提供了强大的结构。我们将深入研究通道和通道操作的内部工作原理,包括运行时调度程序和内存管理系统如何支持它们。