Go Channels

…a means of communication


Björn Andersson

Quick thing about Go

  • Does not have threads
    • Instead has "go routines"
    • Kind of like a "green/lightweight" thread
    • Many go routines in one thread and many threads to a program
  • You have to write your code as if it could go into another thread at any point

What?

  • Channels are for letting different parts of the code communicate
  • In Go, you never know if you're in a thread or not
  • The fastest way of sharing sharing state is over a channel

No globals?

  • Of course, we still have globals and mutexes
  • But prefer to send a message on a channel!

Syntax and usage

  • stopChan := make(chan bool), create a channel
  • <-c: read from a channel
  • c <-: write to a channel

Passing channels

  • Used when specifying which channel you want in your functions and interfaces
  • name <-chan string: read-only
  • name chan<- string: write-only

Types of channels

  • Unbuffered
  • Buffered
  • Closed

Unbuffered

  • Holds a single value at a time
  • You only continue as soon as someone reads from the channel

Unbuffered example


greetingChan := make(chan string)

go func (c <-chan string) {
  fmt.Printf("Hello, %s", <-c)
}(greetingChan)

greetingChan <- "World"

Buffered

  • Can hold up to n values at a time
  • Before it has n values in it doesn't wait for someone to read
  • Is practically the same as a buffered with 1 as n

Buffered concurrent execution

With channels you can share a limited resource across unlimited concurrency


var dbConns []*DB.Conn // assume it contains 5 connections
dbPool := make(chan *DB.Conn, 5)
for i := 0; i < 5; i++ {
  dbPool <-dbConns[i]
}

t.Run("parallel test run 1", func (t *testing.T) {
  t.Parallel()
  db := <- dbPool
  defer func () { dbPool <- db }() // returns to pool
  // run lots of tests
})

Closed

  • Will return the default value immediately
  • You can always ask a channel if its open when reading by asking for a second boolean parameter

Closed example


stopChan := make(chan struct{})

close(stopChan) // No value written to the channel
v, ok := <- stopChan
fmt.Printf("v=%v, open=%v\n", v, ok) // v=false, open=false
v, ok = <- stopChan
fmt.Printf("v=%v, open=%v\n", v, ok) // v=false, open=false

Tips

  • Think about about your lines of communication
    • Only one Go routine writes and one reads — don't do both in the same
    • Who is leading the communication?
    • Who is charge of telling the routine to shut down?
  • Does a started Go routine have a reason to communicate something back?

Example

func getMessages(
    ctx context.Context,
    query Query,
    errChan chan<- error,
    stopChan <-chan struct{},
) {}
  • context.Context means external shutdown
  • errChan sounds like there can be errors that forces the function to stop work on its own
  • stopChan tells me that there are other reasons than context to stop working

select statement

  • Like a switch statement, but for channels
  • Reads from one channel at a time in random order
  • default evaluates immediately if no channel is readable
  • Will wait for a channel to return something before it continues, unless it has a default
  • Remember, a closed channel is always readable