# What is "Channel"?
In Go (Golang), a channel is a powerful construct used for communication and synchronization between goroutines (concurrently executing functions). It is a typed conduit that allows one goroutine to send a message or data to another goroutine. Channels provide a safe and efficient way for goroutines to communicate without explicitly using locks or condition variables.
Channel is most important in Golang, if we want to figure out what it is, we must dive into the source code of Golang, and find its implementation.
So, firstly we know how to create a channel, it works like this:
ch1 := make(chan int, 3);
or
ch2 := make(chan int);
2
3
ch1 is a buffered channel, and ch2 is an unbuffered channel, which we will discuss later. Now we can find the code in the SDK of Golang, the path of the file is SDK(go1.19)/src/runtime/chan.go
.
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
So, "hchan" is the structure of the channel, it's like the picture below:
The buffered channel is the channel that has a none-nil buf, and the size of the buf is "dataqsiz", unbuffered channel is the channel that doesn't has the buf. Because buf can store the data first when multiple goroutines send data to the channel at the same time, so an unbuffered channel is used for synchronous communication between goroutines, and buffered channels allow asynchronous communication between goroutines.
Let's see how the asynchronous communication works.
for i := 0; i < 5; i++ {
go func(num int) {
ch1 <- num
}(i)
}
2
3
4
5
In the code above, we can say there are five goroutines (gorouting-0 to goroutine-4) sending integer i (0 to 4) to ch1 at the same time.
So we can see from the picture about, when we send the data to a buffered channel, the channel will be lock when we start, then the channel will store the data in the buffer, sendx will increase by 1, this sendx means the index of buffer it was stored. When the buffer is full it will create an object of "sudog" that include the data, specific goroutine and channel, then push the sudog to the doubly list '"sendq".
If there is a goroutine receiving data from channel, it will take the data from buffer first according to the recvx, recvx will increase by 1, then if the channel has waiting senders, it will copy the first sender in the sendq to buffer and set the sendx same with recvx.
for {
select {
case s := <- ch1:
fmt.Println("output: " + s)
}
}
2
3
4
5
6
The output will be:
output: 0
output: 1
output: 2
output: 3
output: 4
Now the channel is empty, if we add a new receiver to the channel, it will create an object of "sudog" that include the data, specific goroutine and channel, then push the sudog to the doubly list '"recvq".
If another goroutine send data to the channel, the channel will not be locked and the data will copy from the sender to receiver directly.
The unbuffered channel is also receiving data directly from sender, because there is no buffer in unbuffered channel, so for the unbuffered channel is working as the picture below:
The unbuffered channel will push the sender to sendq when it is block or it doesn't has an avaliable receiver, as soon as it has an available receiver it will take the first sender in sendq, then copy the data from the sender to the receiver directly.