Concurrency

  • Parallelism is about performance.
  • Concurrency is about program design. **

Google I/O 2010 – Rob Pike

In this chapter we will show off Go’s ability for concurrent programming usingchannels and goroutines. Goroutines are the central entity in Go’s ability forconcurrency.

But what is a goroutine, from [effective_go]:

They’re called goroutines because the existing terms – threads, coroutines, processes, and so on – convey inaccurate connotations. A goroutine has a simple model: it is a function executing in parallel with other goroutines in the same address space. It is lightweight, costing little more than the allocation of stack space. And the stacks start small, so they are cheap, and grow by allocating (and freeing) heap storage as required.

A goroutine is a normal function, except that you startit with the keyword go.

  1. ready("Tea", 2) // Normal function call.
  2. go ready("Tea", 2) // ... as goroutine.
  1. func ready(w string, sec int) {
  2. time.Sleep(time.Duration(sec) * time.Second)
  3. fmt.Println(w, "is ready!")
  4. }
  5. func main() {
  6. go ready("Tea", 2) //<1>
  7. go ready("Coffee", 1) //<1>
  8. fmt.Println("I'm waiting")
  9. time.Sleep(5 * time.Second) //<2>

Figure: Go routines in action.

The following idea for a program was taken from [go_course_day3]. We runa function as two goroutines, the goroutines wait for an amount of time and thenprint something to the screen. At 1 we start the goroutines. The mainfunction waits long enough at 2, so that both goroutines will have printedtheir text. Right now we wait for 5 seconds, but in fact we have no idea howlong we should wait until all goroutines have exited. This outputs:

  1. I'm waiting // Right away
  2. Coffee is ready! // After 1 second
  3. Tea is ready! // After 2 seconds

If we did not wait for the goroutines (i.e. remove the last line at 2) theprogram would be terminated immediately and any running goroutines woulddie with it.

To fix this we need some kind of mechanism which allows us tocommunicate with the goroutines. This mechanism is available to us in the formof channels . A channel can be compared to a two-way pipe in Unixshells: you can send to and receive values from it. Those values can only be ofa specific type: the type of the channel. If we define a channel, we must alsodefine the type of the values we can send on the channel. Note that we must usemake to create a channel:

  1. ci := make(chan int)
  2. cs := make(chan string)
  3. cf := make(chan interface{})

Makes ci a channel on which we can send and receive integers,makes cs a channel for strings and cf a channel for typesthat satisfy the empty interface.Sending on a channel and receiving from it, is done with the same operator:<-.

Depending on the operands it figures out what to do:

  1. ci <- 1 // *Send* the integer 1 to the channel ci.
  2. <-ci // *Receive* an integer from the channel ci.
  3. i := <-ci // *Receive* from the channel ci and store it in i.

Let’s put this to use.

  1. var c chan int 1
  2. func ready(w string, sec int) {
  3. time.Sleep(time.Duration(sec) * time.Second)
  4. fmt.Println(w, "is ready!")
  5. c <- 1 2
  6. }
  7. func main() {
  8. c = make(chan int) 3
  9. go ready("Tea", 2) 4
  10. go ready("Coffee", 1) 4
  11. fmt.Println("I'm waiting, but not too long")
  12. <-c 5
  13. <-c 5
  14. }

At 1 we declare c to be a variable that is a channel of ints. That is: thischannel can move integers. Note that this variable is global so that thegoroutines have access to it. At 2 in the ready function we send the integer1 on the channel. In our main function we initialize c at 3 and start ourgoroutines 4. At 5 we Wait until we receive a value from the channel, thevalue we receive is discarded. We have started two goroutines, so we expect twovalues to receive.

There is still some remaining ugliness; we have to read twice from the channel5). This is OK in this case, but what if we don’t know how many goroutines westarted? This is where another Go built-in comes in: select (((keywords,select))). With select you can (among other things) listen for incoming dataon a channel.

Using select in our program does not really make it shorter, because we runtoo few go-routines. We remove last lines and replace them with the following:

  1. L: for {
  2. select {
  3. case <-c:
  4. i++
  5. if i > 1 {
  6. break L
  7. }
  8. }
  9. }

We will now wait as long as it takes. Only when we have received more than onereply on the channel c will we exit the loop L.