Go - Concurrency and Channels

Contemporary Programming Languages - CS2001 - 9 November 2017

Concurrency

  • When parts of your code are running concurrently, you are often unable to determine when things are going to happen and in what order
  • Be careful

Goroutines

  • A goroutine is a lightweight thread of execution managed by the Go runtime.
    • Independent, concurrent threads of control
    • Share the same address space
    • Different from OS threads
    • When main() returns, the program exits. It does not wait for other goroutines to complete
    • A “go” statement starts the execution of function call as a goroutine
      • Must be a function or method call
      • Unlike a regular call, program execution does not wait for the invoked function to complete.
    package main
    import(
      "fmt"
      "time"
    )
    func say(s string) {
      for i := 0; i < 5; i++ {
        fmt.Print(s)
        time.Sleep(1 * time.Second)
      }
    }
    func main() {
      say("Hello ")
    
      time.Sleep(0.5 * time.Second)
      for i := 0; i < 5; i++ {
        fmt.Println("world!")
        time.Sleep(1 * time.Second)
      }
    }
    
    // Hello Hello Hello Hello Hello world!
    // world!
    // world!
    // world!
    // world!
    
    package main
    import(
      "fmt"
      "time"
    )
    func say(s string) {
      for i := 0; i < 5; i++ {
        fmt.Print(s)
        time.Sleep(1 * time.Second)
      }
    }
    func main() {
      go say("Hello ") // This line changed
    
      time.Sleep(0.5 * time.Second)
      for i := 0; i < 5; i++ {
        fmt.Println("world!")
        time.Sleep(1 * time.Second)
      }
    }
    
    // Hello world!
    // Hello world!
    // Hello world!
    // Hello world!
    // Hello world!
    

Channels

  • Channels are used to communicate between goroutines
  • Your can send and receive over a channel
  • Works like a FIFO queue
  • Unbuffered Chan-communication succeeds only when a sender and receiver are both ready
  • Buffered Chan-communication succeeds without blocking if the budder is not full (sending) or not empty (receiving)
  • Zero value is nil
  • Initialized with make()
Sending/Receiving w/ an Unbuffered Chan
func print(strings chan string) {
  for {
    x := <-strings
    fmt.Println(x)
    // fmt.Println(<-strings) is the idiomatic way
  }
}
func main() {
  c := make(chan.string) // Unbuffered channel
  go print(c)
  c <- "apple"
  c <- "banana"
  c <- "carrot"
}

// apple
// banana
// carrot
func main() {
  c := make(chan string)
  c <- "apple" // wait here forever
  fmt.Println(<-c)
}
Sending/Receiving w/ a Buffered Chan
c := make(chan string, 10)
c <- "apple"
c <- "banana"
c <- "carrot"
fmt.Println(<-c)
fmt.Println(<-c)
fmt.Println(<-c)
fmt.Println(<-c) // deadlocks here (Go will panic)
package main
import(
  "bufio"
  "fmt"
  "io"
  "os"
)
func countWords(reader io.Reader, counts chan int) {
  scanner := bufio.NewScanner(reader)
  scanner.Split(bufio.ScanWords)
  count := 0
  for scanner.Scan() {
    count++
  }
  if err := scanner.Err(); err != nil {
    fmt.Println(err)
  }
}
func main() {
  files := []string {"data1.txt", "data2.txt"}
  counts := make(chan int)
  openCount := 0
  for _, fname := range files {
    file, err := os.Open(fname)
    if err != nil {
      fmt.Println("Couldn't open", fname)
    } else {
      defer file.Close()
      go countWords(file, counts)
      openCount++
    }
  }
  for i := 0; i < openCount; i++ {
    fmt.Println(<-counts)
  }
}
func countWords2(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 countWords2(f, s)
  }
  for i := 0; i < len(files); i++ {
    <- s
  }
}