Go Channel Unlocked: How They Work
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) }
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 theruntime.makechan
function.c <- 1
corresponds to theruntime.chansend
function.x := <-c
corresponds to theruntime.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
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 }
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.waitq
Implemented using a doubly linked list. - Other attributes: such as
lock
,elemtype
,closed
, etc.
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
).
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) }
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
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 }
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 inhchan
: 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.

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) }
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
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 insendq
to the buffer. Becauserecv
is executed later, it is equivalent to placing the data insendq
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

Finally, I recommend a platform that is very suitable for deploying Go services: Leapcell
- Multi-language support: Supports JavaScript, Python, Go or Rust development.
- Deploy unlimited projects for free: Pay only for what you use, no requests, no fees.
- 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.
- Smooth developer experience: Intuitive UI for easy setup; fully automated CI/CD pipeline and GitOps integration; real-time metrics and logs for actionable insights.
- Easy scalability and high performance: Automatically scale to easily handle high concurrency; zero operational overhead, focus on building.

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!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics



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.

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

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

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

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. �...

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

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

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,...
