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. 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).