Home > Backend Development > Golang > Go Channel Unlocked: How They Work

Go Channel Unlocked: How They Work

Mary-Kate Olsen
Release: 2025-01-17 02:11:10
Original
355 people have browsed it

In-depth Golang Channel: Implementation principles and performance optimization suggestions

Golang’s Channel is a key component of its CSP concurrency model and a bridge for communication between Goroutines. Channel is frequently used in Golang, and it is crucial to have a deep understanding of its internal implementation principles. This article will analyze the underlying implementation of Channel based on the Go 1.13 source code.

Basic usage of Channel

Before formally analyzing the implementation of Channel, let’s review its basic usage:

<code class="language-go">package main
import "fmt"

func main() {
    c := make(chan int)

    go func() {
        c <- 1 // 发送操作
    }()

    x := <-c // 接收操作
    fmt.Println(x)
}</code>
Copy after login
Copy after login
Copy after login

This code shows two basic operations of Channel:

  • Send operation: c <- 1
  • Receive operation: x := <-c

Channel is divided into buffered Channel and non-buffered Channel. The above code uses a non-buffered Channel. In a non-buffered Channel, if no other Goroutine is currently receiving data, the sender will block at the send statement.

You can specify the buffer size when initializing the Channel. For example, make(chan int, 2) specifies the buffer size to be 2. Before the buffer is full, the sender can send data without blocking without waiting for the receiver to be ready. But if the buffer is full, the sender will still block.

Channel underlying implementation function

Before diving into the Channel source code, you need to find the specific implementation location of Channel in Golang. When using Channel, the underlying functions such as runtime.makechan, runtime.chansend and runtime.chanrecv are actually called.

You can use the go tool compile -N -l -S hello.go command to convert the code into assembly instructions, or use the online tool Compiler Explorer (for example: go.godbolt.org/z/3xw5Cj). By analyzing the assembly instructions, we can find:

  • make(chan int) corresponds to the runtime.makechan function.
  • c <- 1 corresponds to the runtime.chansend function.
  • x := <-c corresponds to the runtime.chanrecv function.

The implementation of these functions are located in the runtime/chan.go file of the Go source code.

Channel structure

make(chan int) will be converted into a runtime.makechan function by the compiler, and its function signature is as follows:

<code class="language-go">func makechan(t *chantype, size int) *hchan</code>
Copy after login
Copy after login
Copy after login

Among them, t *chantype is the Channel element type, size int is the user-specified buffer size (0 if not specified), and the return value is *hchan. hchan is the internal implementation structure of Channel in Golang, defined as follows:

<code class="language-go">type hchan struct {
        qcount   uint           // 缓冲区中已放入元素的数量
        dataqsiz uint           // 用户构造Channel时指定的缓冲区大小
        buf      unsafe.Pointer // 缓冲区
        elemsize uint16         // 缓冲区中每个元素的大小
        closed   uint32         // Channel是否关闭,==0表示未关闭
        elemtype *_type         // Channel元素的类型信息
        sendx    uint           // 缓冲区中发送元素的索引位置(发送索引)
        recvx    uint           // 缓冲区中接收元素的索引位置(接收索引)
        recvq    waitq          // 等待接收的Goroutine列表
        sendq    waitq          // 等待发送的Goroutine列表

        lock mutex
}</code>
Copy after login
Copy after login

The attributes in hchan are roughly divided into three categories:

  • Buffer related attributes: such as buf, dataqsiz, qcount, etc. When the buffer size of the Channel is not 0, the buffer is used to store the data to be received, and is implemented using a ring buffer.
  • Waiting queue related attributes: recvq contains Goroutine waiting to receive data, sendq contains Goroutine waiting to send data. waitqImplemented using a doubly linked list.
  • Other attributes: such as lock, elemtype, closed, etc.
The

makechan function mainly performs some legality checks and memory allocation of attributes such as buffers and hchan, which will not be discussed in depth here.

Based on a simple analysis of the hchan attribute, it can be seen that there are two important components: buffer and waiting queue. All behaviors and implementations of hchan revolve around these two components.

Channel data sending

The sending and receiving processes of Channel are very similar. First analyze the sending process of Channel (for example c <- 1).

When

tries to send data to the Channel, if the recvq queue is not empty, a Goroutine waiting to receive data will be taken out from the recvq header and the data will be sent directly to the Goroutine. The code is as follows:

<code class="language-go">package main
import "fmt"

func main() {
    c := make(chan int)

    go func() {
        c <- 1 // 发送操作
    }()

    x := <-c // 接收操作
    fmt.Println(x)
}</code>
Copy after login
Copy after login
Copy after login

recvq Contains Goroutine waiting to receive data. When a Goroutine uses a receive operation (such as x := <-c), if sendq is not empty at this time, a Goroutine will be taken from sendq and the data will be sent to it.

If recvq is empty, it means that there is no Goroutine waiting to receive data at this time, and the Channel will try to put the data into the buffer:

<code class="language-go">func makechan(t *chantype, size int) *hchan</code>
Copy after login
Copy after login
Copy after login

The function of this code is very simple, it is to put data into the buffer. This process involves the operation of a ring buffer, dataqsiz represents the user-specified buffer size (defaults to 0 if not specified).

If a non-buffered Channel is used or the buffer is full (c.qcount == c.dataqsiz), the data to be sent and the current Goroutine will be packaged into a sudog object, placed in sendq, and the current Goroutine will be set to wait Status:

<code class="language-go">type hchan struct {
        qcount   uint           // 缓冲区中已放入元素的数量
        dataqsiz uint           // 用户构造Channel时指定的缓冲区大小
        buf      unsafe.Pointer // 缓冲区
        elemsize uint16         // 缓冲区中每个元素的大小
        closed   uint32         // Channel是否关闭,==0表示未关闭
        elemtype *_type         // Channel元素的类型信息
        sendx    uint           // 缓冲区中发送元素的索引位置(发送索引)
        recvx    uint           // 缓冲区中接收元素的索引位置(接收索引)
        recvq    waitq          // 等待接收的Goroutine列表
        sendq    waitq          // 等待发送的Goroutine列表

        lock mutex
}</code>
Copy after login
Copy after login

goparkunlock will unlock the input mutex and suspend the current Goroutine, setting it to a wait state. gopark and goready appear in pairs and are reciprocal operations.

From the user's perspective, after calling gopark, the code statement for sending data will block.

Channel data reception

The receiving process of Channel is basically similar to the sending process, so I won’t go into details here. The buffer-related operations involved in the reception process will be described in detail later.

It should be noted that the entire sending and receiving process of Channel is locked using runtime.mutex. runtime.mutex is a lightweight lock commonly used in runtime-related source code. The whole process is not the most efficient lock-free solution. There is an issue about lock-free Channel in Golang: go/issues#8899.

Channel ring buffer implementation

Channel uses a ring buffer to cache written data. Ring buffers have many advantages and are ideal for implementing fixed-length FIFO queues.

The implementation of the ring buffer in Channel is as follows:

There are two buffer-related variables in

hchan: recvx and sendx. sendx represents a writable index in the buffer, and recvx represents a readable index in the buffer. Elements between recvx and sendx represent data that has been put into the buffer normally.

Go Channel Unlocked: How They Work

You can directly use buf[recvx] to read the first element of the queue, and use buf[sendx] = x to put the element at the end of the queue.

Buffer writing

When the buffer is not full, the operation of putting data into the buffer is as follows:

<code class="language-go">package main
import "fmt"

func main() {
    c := make(chan int)

    go func() {
        c <- 1 // 发送操作
    }()

    x := <-c // 接收操作
    fmt.Println(x)
}</code>
Copy after login
Copy after login
Copy after login

chanbuf(c, c.sendx) is equivalent to c.buf[c.sendx]. The above process is very simple, just copy the data to the buffer location sendx.

Then, move sendx to the next position. If sendx reaches the last position, it is set to 0, which is a typical end-to-end approach.

Buffer reading

When the buffer is not full, sendq must also be empty (because if the buffer is not full, the Goroutine sending the data will not be queued, but will directly put the data into the buffer). At this time, the reading logic of Channel chanrecv is relatively simple. Data can be read directly from the buffer. It is also a process of moving recvx, which is basically the same as the buffer writing above.

When there is a waiting Goroutine in sendq, the buffer must be full at this time. At this time, the reading logic of Channel is as follows:

<code class="language-go">func makechan(t *chantype, size int) *hchan</code>
Copy after login
Copy after login
Copy after login

ep is the address corresponding to the variable that receives the data (for example, in x := <-c, ep is the address of x). sg represents the first sendq taken from sudog. In the code:

  • typedmemmove(c.elemtype, ep, qp) means copying the currently readable element in the buffer to the address of the receiving variable.
  • typedmemmove(c.elemtype, qp, sg.elem) means copying the data waiting to be sent by Goroutine in sendq to the buffer. Because recv is executed later, it is equivalent to placing the data in sendq at the end of the queue.

Simply put, here Channel copies the first data in the buffer to the corresponding receiving variable, and at the same time copies the elements in sendq to the end of the queue, thereby implementing FIFO (first in, first out).

Summary

Channel is one of the most commonly used facilities in Golang. Understanding its source code will help you better use and understand Channel. At the same time, do not be overly superstitious and rely on the performance of Channel. The current design of Channel still has a lot of room for optimization.

Optimization suggestions:

  • Use a more lightweight locking mechanism or lock-free scheme to improve performance.
  • Optimize buffer management and reduce memory allocation and copy operations.

Leapcell: The best serverless platform for Golang web applications

Go Channel Unlocked: How They Work

Finally, I recommend a platform that is very suitable for deploying Go services: Leapcell

  1. Multi-language support: Supports JavaScript, Python, Go or Rust development.
  2. Deploy unlimited projects for free: Pay only for what you use, no requests, no fees.
  3. Extremely cost-effective: Pay as you go, no idle fees. For example: $25 supports 6.94 million requests with an average response time of 60 milliseconds.
  4. Smooth developer experience: Intuitive UI for easy setup; fully automated CI/CD pipeline and GitOps integration; real-time metrics and logs for actionable insights.
  5. Easy scalability and high performance: Automatically scale to easily handle high concurrency; zero operational overhead, focus on building.
Go Channel Unlocked: How They Work

Please check the documentation for more information!

Leapcell Twitter: https://www.php.cn/link/7884effb9452a6d7a7a79499ef854afd

The above is the detailed content of Go Channel Unlocked: How They Work. For more information, please follow other related articles on the PHP Chinese website!

source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template