Channels are a way for goroutines to communicate with eachother. A
channel type is defined with the keyword chan
.
Because goroutines run concurrently, they can’t simply pass data from one goroutine to another. Channels are needed.
How do channels work?
If you type c <- "message"
, “message” gets send to
the channel. Then msg := <- c
means receive the message
and store it in variable msg.
Example
Goroutines
In this example there are two goroutines (f, f2). These goroutines
communicate via a channel, c chan string
.
In goroutine f(c chan string)
we send a message into the
channel. In goroutine f2(c chan string)
the message is
received, stored and printed to the screen.
package main
import "fmt"
func f(c chan string) {
<- "f() was here"
c }
func f2(c chan string) {
:= <- c
msg .Println("f2",msg)
fmt}
func main() {
var c chan string = make(chan string)
go f(c)
go f2(c)
.Scanln()
fmt}
$ go run example.go
f2 f() was here
Example 1: Unbuffered Channel
A channel is a built-in data structure that allows you to send and receive values between goroutines. Channels can be either buffered or unbuffered. In an unbuffered channel, each send operation must be matched by a receive operation, making it a synchronous communication mechanism. Here’s an example of using an unbuffered channel:
package main
import (
"fmt"
"time"
)
func worker(done chan bool) {
.Println("Worker started")
fmt.Sleep(time.Second * 2) // simulate some work
time.Println("Worker finished")
fmt
<- true // send a signal to the channel that the work is done
done }
func main() {
:= make(chan bool)
done
go worker(done) // start a worker goroutine
<-done // wait for the worker goroutine to finish and receive the signal
.Println("Main function exiting")
fmt}
In this example, the worker() function simulates some work by sleeping for 2 seconds. Once the work is done, it sends a signal to the done channel by writing true to it. The main() function creates the done channel and starts a worker goroutine by calling go worker(done). It then waits for the worker to finish by receiving a signal from the done channel using the <-done syntax.
Example 2: Buffered Channel
In contrast to unbuffered channels, buffered channels allow you to send a specified number of values without needing to wait for a receive operation. Here’s an example:
package main
import (
"fmt"
)
func producer(c chan int) {
for i := 0; i < 5; i++ {
<- i // send the value to the channel
c }
close(c) // close the channel after all values have been sent
}
func main() {
:= make(chan int, 2) // create a buffered channel with capacity 2
c go producer(c)
for i := range c { // receive the values from the channel
.Println(i)
fmt}
}
In this example, the producer() function sends 5 values to the channel c. Since c is a buffered channel with capacity 2, the first 2 values are sent immediately, and the remaining values are buffered until they can be received by the main() function’s loop. Finally, the channel is closed to signal that all values have been sent. The main() function receives the values from the channel using the range syntax, which automatically stops when the channel is closed.
Exercises
- When do you need channels?
- How can you send data into a channel?
- How can you read data from a channel?