1. Channels
Channels are the pipes that connect concurrent goroutines. You can send values into channels from one goroutine and receive those values into another goroutine. |
Create a new channel with make(chan val-type)
. Channels are typed by the values they convey.
Send a value into a channel using the channel <-
syntax.
The <-channel
syntax receives a value from the channel.
When we run the program the "ping"
message is successfully passed from one goroutine to another via our channel.
By default sends and receives block until both the sender and receiver are ready. This property allowed us to wait at the end of our program for the "ping"
message without having to use any other synchronization.
[maxwell@oracle-db-19c Day04]$ vim channels.go
[maxwell@oracle-db-19c Day04]$ cat channels.go
package main
import "fmt"
func main(){
messages := make(chan string)
go func() {messages <- "ping"}()
msg := <-messages
fmt.Println(msg)
}
[maxwell@oracle-db-19c Day04]$ go run channels.go
ping
[maxwell@oracle-db-19c Day04]$
2.Channel Buffering
By default channels are unbuffered, meaning that they will only accept sends (chan <-
) if there is a corresponding receive (<- chan
) ready to receive the sent value. Buffered channels accept a limited number of values without a corresponding receiver for those values.
[maxwell@oracle-db-19c Day04]$ vim channelbuffering.go
[maxwell@oracle-db-19c Day04]$ cat channelbuffering.go
package main
import "fmt"
func main(){
messages := make(chan string, 2)
messages <- "Buffered"
messages <- "channel"
fmt.Println(<-messages)
fmt.Println(<-messages)
}
[maxwell@oracle-db-19c Day04]$ go run channelbuffering.go
Buffered
channel
[maxwell@oracle-db-19c Day04]$
3. Channel Synchronization
We can use channels to synchronize execution across goroutines. Here’s an example of using a blocking receive to wait for a goroutine to finish. When waiting for multiple goroutines to finish, you may prefer to use a WaitGroup.
[maxwell@oracle-db-19c Day04]$ vim channelsynchronization.go
[maxwell@oracle-db-19c Day04]$ cat channelsynchronization.go
package main
import (
"fmt"
"time"
)
func worker(done chan bool) {
fmt.Print("working...")
time.Sleep(time.Second)
fmt.Println("Done")
done <- true
}
func main() {
done := make(chan bool, 1)
go worker(done)
<-done
}
[maxwell@oracle-db-19c Day04]$ go run channelsynchronization.go
working...Done
[maxwell@oracle-db-19c Day04]$
4.Channel Directions
When using channels as function parameters, you can specify if a channel is meant to only send or receive values. This specificity increases the type-safety of the program.
This ping
function only accepts a channel for sending values. It would be a compile-time error to try to receive on this channel.
The pong
function accepts one channel for receives (pings
) and a second for sends (pongs
).
[maxwell@oracle-db-19c Day04]$ vim channeldirections.go
[maxwell@oracle-db-19c Day04]$ cat channeldirections.go
package main
import "fmt"
func ping(pings chan<- string, msg string){
pings <- msg
}
func pong(pings <-chan string, pongs chan<- string){
msg := <-pings
pongs <- msg
}
func main() {
pings := make(chan string, 1)
pongs := make(chan string, 1)
ping(pings, "passed message")
pong(pings, pongs)
fmt.Println(<-pongs)
}
[maxwell@oracle-db-19c Day04]$ go run channeldirections.go
passed message
[maxwell@oracle-db-19c Day04]$
5.Select
Go’s select lets you wait on multiple channel operations. Combining goroutines and channels with select is a powerful feature of Go.
Each channel will receive a value after some amount of time, to simulate e.g. blocking RPC operations executing in concurrent goroutines.
We’ll use select
to await both of these values simultaneously, printing each one as it arrives.
Note that the total execution time is only ~2 seconds since both the 1 and 2 second Sleeps
execute concurrently.
[maxwell@oracle-db-19c Day04]$ vim select.go
[maxwell@oracle-db-19c Day04]$ cat select.go
package main
import (
"fmt"
"time"
)
func main(){
c1 := make(chan string)
c2 := make(chan string)
go func(){
time.Sleep(1 * time.Second)
c1 <- "one"
}()
go func(){
time.Sleep(2 * time.Second)
c2 <- "two"
}()
for i := 0; i< 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
}
}
[maxwell@oracle-db-19c Day04]$ time go run select.go
received one
received two
real 0m2.128s
user 0m0.101s
sys 0m0.086s
[maxwell@oracle-db-19c Day04]$