Go Channel(for IPC)
Go channels are used for synchronizing IPC between goroutines(Same as
Rust Tasks = Green Threads). Go routines are managed by Go runtime.
NOTE: Channels are not needed if 2 or more goroutines are not
present. Channels are designed exclusively for inter-goroutine
communication and synchronization
If you are doing everything sequentially inside the single main
goroutine, channel is unnecessary
we can send/recv data via channel with operator, <- between
goroutines. The data flows in the direction of the arrow.
channels are datastructures allocated on heap that reference both sender
and receiver goroutines
close(): Only sender can close a channel to indicate that no more
values will be sent.
|
Write/Read on Channel
|
|
Types of Channels
Unbuffered, Buffered
| Unbuffered / Unbounded | Buffered / Bounded | |
|---|---|---|
| What | Like a hand-to-hand pass. The sender must wait until the receiver does not read the value from channel. If no one is there to take it, the sender BLOCKS/DEADLOCK. | Like a mailbox with a fixed slot capacity. The sender can drop off letters and leave immediately, as long as the mailbox isn't full. The receiver can pick them up later. |
| Code |
|
|
| Can Deadlock |
Yes. Easier in unbuffered. Conditions for deadlock: 1. Sender goroutine sent data on channel and reciever goroutine is not present to read from it
2. Receiving without a concurrent sender. reading from channel where no writer
|
Yes Conditions for deadlock: 1. Overfilling the buffer: Sending more items than the buffer's capacity without anyone reading them
2. Reading from an empty buffer: Trying to read from an empty buffered channel when no other goroutines are alive to write to it. |
2. Bidirectional / Directional
Directional vs. Undirectional is completely different from
Buffered vs. Unbuffered.
Buffered/Unbuffered defines how the channel behaves internally
(capacity/blocking rules).
Directional/Bidirectional defines who is allowed to read or write to it
(type safety).
By default, all channels you create are bidirectional (you can read and
write). However, you can restrict them in function signatures to prevent
bugs (e.g., preventing a function that should only read from
accidentally writing).
Bidirectional
The unbuffered and buffered channels above are bidirectional ie allows flow in both directions
Directional Channels
1. Write-Only Channel: chan<- int. Look at where the arrow is
pointing. It points into the chan. You are taking an integer (int) and
pushing it into the channel (chan). You are writing(send) data to chan.
ch <- 10.
2. Read-Only Channel: <-chan int. Look at where the arrow is
pointing now. It points away from the chan. Data is flowing out of the
channel (chan). You can only read (receive) data from it. val := <-ch
| Write Only (chan <- int) | Read Only (<-chan int) | |
|---|---|---|
| What | Reads not allowed on channel | Writes not allowed on channel |
| Code |
|
|
wait on multiple channels. select
package main
import "fmt"
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
ch1 <- 1
ch2 <- 2
// 1. Must close channels when finished sending
close(ch1)
close(ch2)
}()
for {
select {
case v1, ok := <-ch1:
if ok {
fmt.Println("ch1:", v1)
} else {
// 2. Setting a channel to nil disables this case in a select block
ch1 = nil
}
case v2, ok := <-ch2:
if ok {
fmt.Println("ch2:", v2)
} else {
ch2 = nil
}
}
// 3. If both channels are nil, break out of the infinite loop safely
if ch1 == nil && ch2 == nil {
break
}
}
fmt.Println("Done!")
}
$ go run
ch1: 1
ch2: 2
Done!
Internal Implementation of Channels
-
When we call make(chan int, 10), the runtime allocates a
struct hchan on heap
func main() {
ch1 := make(chan int)
}
// Channel struct
// (heap, hchan with buffer + sendq/recvq queues)
type hchan struct {
qcount uint // bytes queued
dataqsiz uint // buffer size
buf unsafe.Pointer // ring buffer (heap)
elemsize uint16 // elem size
closed uint32
elemtype *_type
sendx uint // send index
recvx uint // recv index
recvq waitq // blocked RECEIVERS
sendq waitq // blocked SENDERS ← your goroutine lives here
}
Why Channels are Fast
-
1. Direct Copying: If a sender is waiting and a receiver arrives, the Go
runtime copies the data directly from the sender's stack to the
receiver's stack, skipping the channel buffer entirely
2. No busy-waiting: Instead of "busy-waiting" (spinning), a blocked goroutine is "parked" by the Go scheduler.