Table of Contents
In-depth Golang Channel: Implementation principles and performance optimization suggestions
Basic usage of Channel
Channel underlying implementation function
Channel structure
Channel data sending
Channel data reception
Channel ring buffer implementation
Buffer writing
Buffer reading
Summary
Leapcell: The best serverless platform for Golang web applications
Home Backend Development Golang Go Channel Unlocked: How They Work

Go Channel Unlocked: How They Work

Jan 17, 2025 am 02:11 AM

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:

package main
import "fmt"

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

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

    x := <-c // 接收操作
    fmt.Println(x)
}
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:

func makechan(t *chantype, size int) *hchan
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:

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
}
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:

package main
import "fmt"

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

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

    x := <-c // 接收操作
    fmt.Println(x)
}
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:

func makechan(t *chantype, size int) *hchan
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:

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
}
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:

package main
import "fmt"

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

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

    x := <-c // 接收操作
    fmt.Println(x)
}
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:

func makechan(t *chantype, size int) *hchan
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!

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

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

AI Hentai Generator

AI Hentai Generator

Generate AI Hentai for free.

Hot Article

R.E.P.O. Energy Crystals Explained and What They Do (Yellow Crystal)
1 months ago By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Best Graphic Settings
1 months ago By 尊渡假赌尊渡假赌尊渡假赌
Will R.E.P.O. Have Crossplay?
1 months ago By 尊渡假赌尊渡假赌尊渡假赌

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

What are the vulnerabilities of Debian OpenSSL What are the vulnerabilities of Debian OpenSSL Apr 02, 2025 am 07:30 AM

OpenSSL, as an open source library widely used in secure communications, provides encryption algorithms, keys and certificate management functions. However, there are some known security vulnerabilities in its historical version, some of which are extremely harmful. This article will focus on common vulnerabilities and response measures for OpenSSL in Debian systems. DebianOpenSSL known vulnerabilities: OpenSSL has experienced several serious vulnerabilities, such as: Heart Bleeding Vulnerability (CVE-2014-0160): This vulnerability affects OpenSSL 1.0.1 to 1.0.1f and 1.0.2 to 1.0.2 beta versions. An attacker can use this vulnerability to unauthorized read sensitive information on the server, including encryption keys, etc.

How do you use the pprof tool to analyze Go performance? How do you use the pprof tool to analyze Go performance? Mar 21, 2025 pm 06:37 PM

The article explains how to use the pprof tool for analyzing Go performance, including enabling profiling, collecting data, and identifying common bottlenecks like CPU and memory issues.Character count: 159

How do you write unit tests in Go? How do you write unit tests in Go? Mar 21, 2025 pm 06:34 PM

The article discusses writing unit tests in Go, covering best practices, mocking techniques, and tools for efficient test management.

What libraries are used for floating point number operations in Go? What libraries are used for floating point number operations in Go? Apr 02, 2025 pm 02:06 PM

The library used for floating-point number operation in Go language introduces how to ensure the accuracy is...

What is the problem with Queue thread in Go's crawler Colly? What is the problem with Queue thread in Go's crawler Colly? Apr 02, 2025 pm 02:09 PM

Queue threading problem in Go crawler Colly explores the problem of using the Colly crawler library in Go language, developers often encounter problems with threads and request queues. �...

What is the go fmt command and why is it important? What is the go fmt command and why is it important? Mar 20, 2025 pm 04:21 PM

The article discusses the go fmt command in Go programming, which formats code to adhere to official style guidelines. It highlights the importance of go fmt for maintaining code consistency, readability, and reducing style debates. Best practices fo

PostgreSQL monitoring method under Debian PostgreSQL monitoring method under Debian Apr 02, 2025 am 07:27 AM

This article introduces a variety of methods and tools to monitor PostgreSQL databases under the Debian system, helping you to fully grasp database performance monitoring. 1. Use PostgreSQL to build-in monitoring view PostgreSQL itself provides multiple views for monitoring database activities: pg_stat_activity: displays database activities in real time, including connections, queries, transactions and other information. pg_stat_replication: Monitors replication status, especially suitable for stream replication clusters. pg_stat_database: Provides database statistics, such as database size, transaction commit/rollback times and other key indicators. 2. Use log analysis tool pgBadg

Transforming from front-end to back-end development, is it more promising to learn Java or Golang? Transforming from front-end to back-end development, is it more promising to learn Java or Golang? Apr 02, 2025 am 09:12 AM

Backend learning path: The exploration journey from front-end to back-end As a back-end beginner who transforms from front-end development, you already have the foundation of nodejs,...

See all articles