Go 通道是如何实现的?

问题描述 投票:0回答:5

在(简要地)回顾了 Go 语言规范、有效的 Go 和 Go 内存模型之后,我仍然有点不清楚 Go 通道在幕后是如何工作的。

它们是什么样的结构?它们的行为有点像线程安全队列/数组。

它们的实现依赖于架构吗?

go channel
5个回答
106
投票

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

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

Channel 操作都在这个 chan.go 文件中实现,因此您可以看到 makechan、发送和接收操作,以及 select 构造、close、len 和 cap 内置函数。

要深入了解通道的内部工作原理,你必须阅读 Dmitry Vyukov 本人撰写的 Go Channels on steroids(Go 核心开发、goroutines、调度程序和通道等)。


18
投票

这里有一篇很好的演讲,大致描述了通道是如何实现的:
https://youtu.be/KBZlN0izeiY

演讲描述:

GopherCon 2017:Kavya Joshi - 了解渠道

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


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

和指数:

sendx    uint   // send index
recvx    uint   // receive index

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

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

recvq    waitq  // list of recv waiters
sendq    waitq  // list of send waiters

每次 go-routine 想要将任务添加到满通道(缓冲区已满),或从空通道(缓冲区为空)获取任务时,都会分配一个伪 go-routine sudog 结构,并且go-routine 将 sudog 作为节点相应地添加到发送或接收等待者列表中。然后,go 例程使用特殊调用更新 go 运行时调度程序,这会提示何时应将它们退出执行 (

gopark
) 或准备运行 (
goready
)。 请注意,这是一个非常简单的解释,隐藏了一些复杂性。

  1. 它们的实现依赖于架构吗?

除了操作系统特定的锁实现(如 @mna 已经解释的那样)之外,我不知道任何架构特定的约束优化或差异。


0
投票

查看通道的一种更简单的方法是这样的,因为您可能希望在等待条件完成时暂停程序,通常用于防止竞争条件,这意味着一个线程可能不会在另一个线程之前完成,然后发生一些事情您稍后的线程或代码取决于有时不完成。 一个例子是,您有一个线程从数据库或其他服务器检索一些数据并将数据放入变量、切片或映射中,并且由于某种原因它会延迟。那么您就有一个使用该变量的进程,但由于它尚未初始化,或者尚未获取其数据。程序失败。 所以用代码来看一个简单的方法如下: 包主

import "fmt"

var doneA = make(chan bool)
var doneB = make(chan bool)
var doneC = make(chan bool)

func init() { // this runs when you program starts.
    go func() {
        doneA <- true  //Give donA true
    }()
}

func initB() { //blocking
    go func() {
        a := <- doneA  //will wait here until doneA is true
        // Do somthing here
        fmt.Print(a)
        doneB <- true //State you finished
    }()
}

func initC() {
    go func() {
        <-doneB // still blocking, but dont care about the value
        // some code here
        doneC <- true // Indicate finished this function
    }()
}

func main() {
    initB()
    initC()
}

希望这会有所帮助。不是上面选定的答案,但我相信应该有助于消除谜团。我想知道我是否应该提出问题并自我回答?


0
投票
Channels are a core part of Go’s concurrency model and allow goroutines to communicate and synchronize their execution. They can be thought of as typed message queues that allow for safe communication between goroutines.

Channel Basics
— Unbuffered Channels

Unbuffered channels are the simplest form of channels. When you create an unbuffered channel, it has a capacity of zero. This means that every send operation on the channel blocks until another goroutine is ready to receive the value. Likewise, every receive operation blocks until another goroutine is ready to send a value.

Unbuffered channels ensure that the sender and receiver goroutines are synchronized. If the sender sends a value before the receiver is ready to receive, it will block until the receiver is ready. Similarly, if the receiver tries to receive a value before the sender has sent it, it will be blocked until the sender is ready.

— Buffered Channels

Buffered channels, on the other hand, have a specified capacity greater than zero. This means that they can hold a certain number of values before blocking send operations. Buffered channels decouple the sender and receiver, allowing for asynchronous communication.
© www.soinside.com 2019 - 2024. All rights reserved.