We can use channels in order to send information among concurrent goroutines. They can be thought of as pipes in which we pass data around. A new channel is created using the syntax:
make(chan <dataType>)
// e.g. a channel for passing strings
myChannel := make(chan string)
We can then send values through the channel using the syntax:
myChannel <- "this is my message to you"
Receiving values from a channel
In order to get a value in a channel, we use the syntax:
// store value from channel into variable myMessage
myMessage := <-myChannel
Consider the following code:
package main
import "fmt"
func main() {
littleBirdie := make(chan string)
myMessages := []string{
"Lester is ok",
"Nana is at least 60 now",
}
for _, message := range myMessages {
go func() {
littleBirdie <- message
}()
}
// get first message
message := <-littleBirdie
// print first message
fmt.Println(message)
// print second message
fmt.Println(<-littleBirdie)
}
The send and receive operations will block until both sender and receiver are ready. Note how we do not need to use synchronization primitives like wait groups to ensure that the program waits for each goroutine to complete before terminating.
Channels are unbuffered
Channels are, by default, unbuffered meaning they only accept sends (chan <-) once there is a corresponding receive (<- chan) in the code. They will block execution until the value sent is received by a goroutine.
A deadlock occurs when there is a send and receive operation in the same goroutine:
package main
import "fmt"
func main() {
bits := make(chan int)
bits <- 1
fmt.Println("Sent a bit of 1")
fmt.Println("Received", <-bits)
}
Deadlocks can also occur if the receive operations exceed the number of values passed into the channel:
package main
import "fmt"
func main() {
bits := make(chan int)
go func() {
bits <- 1
bits <- 0
}()
fmt.Println("Sent a bit of 1")
fmt.Println("Received", <-bits)
fmt.Println("Received", <-bits)
// asking for too many values
fmt.Println("Received", <-bits)
}
Channel buffering
Buffered channels can accept a limited number of values without needing a corresponding receiver for these values. The make command is used in the usual fashion but with a second parameter being the maximum number of values that can be sent in the channel without needing the receiver:
package main
import "fmt"
func main() {
messages := make(chan string, 5)
// send messages
messages <- "Hello"
messages <- "World!"
messages <- "How"
messages <- "are"
messages <- "you?"
// receive messages
fmt.Println(<-messages)
fmt.Println(<-messages)
fmt.Println(<-messages)
fmt.Println(<-messages)
fmt.Println(<-messages)
}
Of course, a deadlock results if we try to receive too many values (try duplicating the fmt.Println statement and rerun the code).
Channel synchronization
The execution of code across goroutines can be synchronized via channels. Channels can be used to block goroutines. The following code terminates after the goroutine sending cows closes the channel. Most of the sheep won’t sent sent:
package main
import (
"fmt"
"strconv"
"time"
)
func main() {
c := make(chan string, 5)
// send 100 sheep
go count(100, "sheep", c)
// send only 4 cows
go count(4, "cow", c)
// receive values from c channel,
// blocking the current (main) goroutine until it is closed by the sender
for msg := range c {
fmt.Println(msg)
}
}
func count(number int, thing string, c chan string) {
for i := 1; i <= number; i++ {
// send value via c channel to main goroutine
c <- strconv.Itoa(i) + " " + thing
time.Sleep(time.Millisecond * 500)
}
// close channel
close(c)
}
Consider the worker pattern:
package main
import (
"fmt"
"sync"
"time"
)
func worker(name string, jobs <-chan string) {
// will block current go routine and wait for jobs
// until jobs channel is closed
for job := range jobs {
fmt.Println(name, "begins to eat", job)
time.Sleep(time.Second * 3)
fmt.Println(name, "ate the", job)
}
}
func main() {
names := []string{"John", "James", "Mary"}
mealsToEat := []string{"banana", "hotdog", "chicken"}
mealsChannel := make(chan string, 10)
var wg sync.WaitGroup
// spawn a worker in a go routine for each person
for _, name := range names {
wg.Go(func() {
worker(name, mealsChannel)
})
}
// send meals into meals channel
for _, meal := range mealsToEat {
mealsChannel <- meal
}
// close channel to unblock workers when channel is empty
close(mealsChannel)
fmt.Println("closed meals channel")
wg.Wait()
}
Channel directions
Created using natural intelligence