Go Channels for Goroutine Communication
A channel in Go is a typed communication mechanism that allows goroutines to send and receive values. Channels are commonly used to exchange results, coordinate concurrent work, and signal when a task has completed.
Go channel send and receive operations block by default until the other side is ready. This behavior lets one goroutine send a value while another goroutine receives it, providing both communication and synchronization.
Every channel has an element type. A channel declared as chan int can carry only int values, while a channel declared as chan string can carry only strings.
Create a Go Channel with make()
To create a channel, use the following syntax.
mychannel := make(chan valtype)
where make and chan are keywords and valtype is the datatype of the values that are sent and received in the channel. make(chan valtype) returns a reference to the channel using which you can send and receive values.
For example, the following golang statement creates a channel named numbers which allows values of type int.
numbers := make(chan int)
A channel created without a capacity is an unbuffered channel. A send on an unbuffered channel waits until another goroutine is ready to receive the value.
Send Values to a Go Channel
To send values to a Channel, use the following syntax.
mychannel <- myvalue
where myvalue is sent to the channel mychannel. The datatype of the value should match the type used while creating the channel.
For an unbuffered channel, the send operation pauses until a receiver is ready. For a buffered channel, the send pauses only when the buffer is full.
Receive Values from a Go Channel
To receive values from the Go Channel, use the following syntax.
myvalue := <- mychannel
where <- mychannel pops the value from mychannel and := assigns the value to myvalue. The datatype of the value should match the type used while creating the channel.
A receive operation waits when no value is available. If the channel is closed and its buffered values have already been received, the operation returns the zero value of the channel element type immediately.
Go Channel Example with Concurrent Goroutines
In the following example, we will compute the sum of numbers in an array. We split the array among two goroutines and find the sum of the two array slices concurrently. After finding sum of the slices, we write the values to channel. In the main goroutine, we will receive the value from the channel and find the complete sum.
example.go
package main
import "fmt"
func sum(s []int, mychannel chan int) {
sum := 0
for _, v := range s {
sum += v
}
mychannel <- sum // send sum to channel
}
func main() {
s := []int{7, -2, 8, 9, 4, 5, 1, 6}
mychannel := make(chan int)
go sum(s[:len(s)/2], mychannel)
go sum(s[len(s)/2:], mychannel)
x := <-mychannel // receive from mychannel
y := <-mychannel // receive from mychannel
fmt.Println("Sum computed in first goroutine: ", x)
fmt.Println("Sum computed in second goroutine: ", y)
fmt.Println("Total sum: ", x+y)
}
Output
Sum computed in first goroutine: 16
Sum computed in second goroutine: 22
Total sum: 38
The order of the first two output lines is not guaranteed. Either goroutine may finish first, so the first value received from mychannel may belong to either half of the slice. The total remains the same.
Buffered and Unbuffered Go Channels
An unbuffered channel has no storage slot between the sender and receiver. The send and receive must meet before either operation can continue.
messages := make(chan string)
A buffered channel stores a limited number of values. Specify its capacity as the second argument to make().
messages := make(chan string, 2)
The following program can send two values before it starts receiving because the channel has a capacity of two.
package main
import "fmt"
func main() {
messages := make(chan string, 2)
messages <- "first"
messages <- "second"
fmt.Println(<-messages)
fmt.Println(<-messages)
}
first
second
A buffer does not make channel operations non-blocking in every situation. Sending still blocks when the buffer is full, and receiving blocks when the buffer is empty and the channel remains open.
Close a Go Channel and Detect Its Closed State
The sender can call close() when no more values will be sent. Closing is useful when receivers need to know that a stream of values has ended.
close(mychannel)
A receiver can use the two-value receive form to check whether a value was obtained before the channel was fully drained.
value, ok := <-mychannel
okistruewhen a value was received normally.okisfalsewhen the channel is closed and no buffered values remain.
Only the sending side should normally close a channel. Receiving from a closed channel is allowed, but sending to a closed channel causes a panic. Closing an already closed channel also causes a panic.
Range over Values Until a Go Channel Closes
A for range loop receives values repeatedly until the channel is closed and all buffered values have been consumed.
package main
import "fmt"
func produce(numbers chan int) {
for i := 1; i <= 3; i++ {
numbers <- i
}
close(numbers)
}
func main() {
numbers := make(chan int)
go produce(numbers)
for number := range numbers {
fmt.Println(number)
}
}
1
2
3
If the sender never closes the channel, a receiver ranging over it may wait indefinitely after consuming the final value.
Send-Only and Receive-Only Go Channel Types
Function parameters can restrict a channel to one direction. Directional channel types make the intended use clear and let the compiler reject an operation in the wrong direction.
func sendValue(ch chan<- int) {
ch <- 10
}
func receiveValue(ch <-chan int) int {
return <-ch
}
chan<- intis a send-only channel.<-chan intis a receive-only channel.chan intis bidirectional.
A bidirectional channel can be passed to a function expecting a send-only or receive-only channel. The reverse conversion is not permitted.
Wait on Multiple Go Channels with select
A select statement waits for one of several channel operations to become ready. It is useful when a goroutine must receive from multiple channels, send whenever a receiver becomes available, or stop after a timeout.
package main
import (
"fmt"
"time"
)
func main() {
result := make(chan string)
go func() {
time.Sleep(100 * time.Millisecond)
result <- "completed"
}()
select {
case message := <-result:
fmt.Println(message)
case <-time.After(time.Second):
fmt.Println("timed out")
}
}
completed
If more than one case is ready, Go chooses one ready case. A default case can make the select non-blocking, but it should be used carefully because a loop containing an always-ready default case may consume unnecessary CPU time.
Get the Length and Capacity of a Go Channel
To get the number of elements currently queued in a Go channel buffer, pass the channel to the len() function. Use cap() to get the channel buffer capacity.
package main
import "fmt"
func main() {
numbers := make(chan int, 3)
numbers <- 10
numbers <- 20
fmt.Println("Length:", len(numbers))
fmt.Println("Capacity:", cap(numbers))
}
Length: 2
Capacity: 3
The value returned by len() is only a snapshot. In a concurrent program, another goroutine may send or receive immediately after the call. It should not normally be used as a synchronization condition.
Nil Go Channels and Blocking Behavior
The zero value of a channel variable is nil. Sending to or receiving from a nil channel blocks forever. Calling close() on a nil channel causes a panic.
var numbers chan int
fmt.Println(numbers == nil) // true
A nil channel can be useful inside a select statement because a case involving a nil channel is never selected. Programs sometimes assign a channel variable to nil to disable that case dynamically.
Go Channel Deadlocks and Common Mistakes
A deadlock occurs when goroutines are waiting for channel operations that can never complete. When every goroutine is blocked and no work can proceed, the Go runtime reports a deadlock.
package main
func main() {
numbers := make(chan int)
numbers <- 10
}
The send blocks because the channel is unbuffered and no other goroutine is ready to receive. One correction is to start a receiving goroutine before sending, or to use an appropriately sized buffer when buffering matches the program design.
- Do not send on an unbuffered channel when no receiver can run.
- Do not close a channel from the receiving side merely to stop receiving.
- Do not send another value after closing the channel.
- Close a channel only when receivers need an end-of-stream signal.
- Do not assume channel operations complete in a predictable goroutine order.
- Do not use
len(ch)as proof that the next send or receive will not block.
When to Use Go Channels
Channels are suitable when goroutines need to transfer ownership of data, return concurrent results, distribute jobs, collect worker output, signal completion, or coordinate cancellation and timeouts.
Channels are not required for every shared-state problem. A mutex may be simpler when multiple goroutines must protect and update the same in-memory state without transferring values between them. Choose the mechanism that makes ownership and synchronization easiest to understand.
Go Channel FAQ
What is a Go channel?
A Go channel is a typed conduit through which goroutines send and receive values. It provides communication and synchronization between concurrent operations.
What is the difference between buffered and unbuffered Go channels?
An unbuffered channel requires a sender and receiver to be ready at the same time. A buffered channel can temporarily hold values up to its capacity, allowing sends to complete while free buffer space remains.
Who should close a Go channel?
The goroutine responsible for sending the final value should normally close the channel. A receiver should not close a channel when another goroutine may still send to it.
How can a receiver check whether a Go channel is closed?
Use the two-value receive form, such as value, ok := <-ch. The value of ok becomes false after the channel is closed and all buffered values have been received.
Does receiving from a closed Go channel cause a panic?
No. Receiving from a closed channel is allowed. Buffered values are returned first, after which receives return the element type’s zero value with ok set to false. Sending to a closed channel causes a panic.
Go Channel Tutorial Editorial QA Checklist
- Confirm that every sent value matches the channel element type.
- Check that each unbuffered send has a receiver that can run concurrently.
- Verify that only the sending side closes channels used as value streams.
- Confirm that every
for rangechannel loop has a reachable channel close. - Check that examples do not rely on a guaranteed goroutine completion order.
- Verify that buffered-channel explanations distinguish current length from total capacity.
- Confirm that timeout examples release or complete any goroutines they start in production code.
Summary of Go Channel Operations
In this Go Tutorial, we learned how to create channels, send and receive values, use buffered channels, close channels, range over channel values, restrict channel direction, wait on multiple operations with select, inspect channel length and capacity, and avoid common channel deadlocks.
TutorialKart.com