Healy Inkorperated

Notes and Other Thoughts

Follow me on GitHub

Concurrency

Concurrency is the ability for a program to do multiple things at the ‘same’ time.

When parts of code are running concurrently, you are often unable to determine when things will happen and in what order.

Goroutines

A goroutine is a lightweight thread of execution managed by the Go runtime. Different from OS threads, they are independent, concurrent threads of control which share the same address space.

When [main()] returns, the program exits. It does not wait for other (non-main) goroutines to complete. This is fundamentally different than the model used in Node.js.

The go Keyword

A go statement starts the execution of a function call as a goroutine. Unlike a regular call, program execution does not wait for the invoked function to complete, as mentioned above.

package main
import(
  "fmt"
  "time"
)

func say(s string) {
  for i:=0; i < 5; i++ {
    fmt.Print(s)
    time.Sleep(500 * time.Millisecond)
  }
}

func main() {
  go say("Hello ")
  time.Sleep(0.5 * time.Second)
  for i := 0; i < 5; i++ {
    fmt.Println("world!")
    time.Sleep(1 * time.Second)
  }
}
Output
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!

Channels

Channels are a primitive needed in order to communicate between goroutines. You can send/recieve over a channel, in a first-in-first-out queue. Channels can be buffered or unbuffered. An unbuffered channel-communication succeeds only when a sender and receiver are both ready. In contrast, a buffered channel succeeds without blocking if the buffer is not full when sending, or not empty in the case of receiving.

Quick Facts:

  • A channel’s zero-value is nil
  • Channels must be initialized with make()

Example(unbuffered):

func print(strings chan string) {
  for {
    x := <- strings
    fmt.Println(x)
    // what follows is the idiomatic method:
    // fmt.Println(<- strings)
  }
}

func main() {
  c := make(chan string)
  go print(c)
  c <- "apple"
  c <- "banana"
  c <- "carrot"
}

Example(buffered)

c := make(chan string, IO)
c <- "apple"
c <- "banana"
c <- "carrot"

fmt.Println(<- c)
fmt.Println(<- c)
fmt.Println(<- c)

Example with Concurrent Data Processing:

package main
import (
  "bufio"
  "fmt"
  "io"
  "os"
)

func countWords(name string, success chan bool) {
  file, err := os.Open(name)
  if err != nil {
    fmt.Println(name, err)
    success <- false
    return
  }
  defer file.Close()
  scanner := bufio.NewScanner(file)
  scanner.Split(bufio.ScanWords)
  count := 0
  for scanner.Scan() {
    count++
  }
  if err := scanner.Err(); err != nil {
    fmt.Println(err)
    success <- false
    return
  }
  fmt.Println(name, count)
  success <- true
}

func main() {
  files := []string{"data1.txt", "data2.txt"}
  s := make(chan bool)
  for _, f := range files {
    go countWords(f, s)
  }
  for i := 0; i < len(files); i++ {
    <- s
  }
}

Synchronization

There are several ways to use channels to synchronize goroutines

  • using a channel to send results
  • using a channel to signal completion
  • close()

The close Function

The close function is a builtin function that closes a channel to indicate that nothing else will be sent over the channel. Sending over a closed channel causes a runtime panic. Receiving from a closed channel will give you a zero-value for the channel’s type.

Signaling End of Output

func fib(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y := y, x + y
    }
    close(c)
}

func main() {
    c := make(chan int)
    go fib(10, c)
    for i := range c {
        fmt.Println(i)
    }
}
Note: closed channels can be reopened with make(chan [type])

Signaling End of Input

type Coord struct {
    x, y float64
}

func printDistance(coords chan Coord, done chan bool) {
    for c := range coords {
        fmt.Println("Distance", math.Hypot(c.x, c.y))
    }
    done <- true
}

func main() {
    c, d := make(chan Coord), make(chan bool)
    for i := 0; i < 3; i++ {
        go printDistance(c, d)
    }
    for i := 0; i < 10; i++ {
        c <- Coord{rand.Float64(), rand.Float64()}
    }
    close(c)
    for i := 0, i < 3; i++ {
        <- d
    }
}

Select Statement

A select statement waits on multiple channel operations, and runs the first communication operation that is ready. If multiple are ready, one is run at random.

func main() {
    c := make(chan int)
    quit := time.After(5 * time.Millisecond)
    go func() {
        for i := 0; ; i++ {
            c <- i
        }
    }()
    for {
        select {
        case val := <- c:
            fmt.Println(val)
        case <- quit:
            fmt.Println("quit")
            return
        }
    }
}