true, "author"=>"björn", "date"=>2020-02-16 23:00:00 +0800, "layout"=>"presentation", "slug"=>"go-channels", "title"=>"Go channels", "subtitle"=>"…a means of communication", "description"=>"Takeaways from learning to use channels in Go.", "categories"=>["programming", "go"], "content"=>"
\n

Go Channels

\n

…a means of communication

\n\n
\n\n

Björn Andersson

\n
\n\n
\n

Quick thing about Go

\n\n \n
\n\n
\n
\n

What?

\n\n \n
\n\n
\n

No globals?

\n \n
\n
\n\n
\n
\n

Syntax and usage

\n\n \n
\n\n
\n

Passing channels

\n \n
\n
\n\n
\n

Types of channels

\n\n \n
\n\n
\n
\n

Unbuffered

\n\n \n
\n\n
\n

Unbuffered example

\n\n \n
\ngreetingChan := make(chan string)\n\ngo func (c <-chan string) {\n  fmt.Printf(\"Hello, %s\", <-c)\n}(greetingChan)\n\ngreetingChan <- \"World\"\n
\n \n
\n
\n\n
\n
\n

Buffered

\n\n \n
\n\n
\n

Buffered concurrent execution

\n

With channels you can share a limited resource across unlimited concurrency

\n\n \n
\nvar dbConns []*DB.Conn // assume it contains 5 connections\ndbPool := make(chan *DB.Conn, 5)\nfor i := 0; i < 5; i++ {\n  dbPool <-dbConns[i]\n}\n\nt.Run(\"parallel test run 1\", func (t *testing.T) {\n  t.Parallel()\n  db := <- dbPool\n  defer func () { dbPool <- db }() // returns to pool\n  // run lots of tests\n})\n
\n\n
\n
\n\n
\n
\n

Closed

\n\n \n
\n\n
\n

Closed example

\n\n
\nstopChan := make(chan struct{})\n\nclose(stopChan) // No value written to the channel\nv, ok := <- stopChan\nfmt.Printf(\"v=%v, open=%v\\n\", v, ok) // v=false, open=false\nv, ok = <- stopChan\nfmt.Printf(\"v=%v, open=%v\\n\", v, ok) // v=false, open=false\n
\n
\n
\n\n
\n
\n

Tips

\n\n \n
\n\n
\n

Example

\n\n \n
func getMessages(\n    ctx context.Context,\n    query Query,\n    errChan chan<- error,\n    stopChan <-chan struct{},\n) {}\n
\n\n\n \n
\n
\n\n
\n

select statement

\n\n \n
\n", "dir"=>"/presentations/go-channels/", "excerpt"=>nil, "name"=>"go-channels.html", "path"=>"presentations/go-channels.html", "url"=>"/presentations/go-channels/"}">

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