Ir al contenido principal

Concurrency in Go: Wait groups

This is the second article of a series analyzing Go concurrency, ie, parallel execution of different threads using Go. The series is composed of these articles:

  1. The Go concurrency foundation: goroutines.
  2. This one, in which synchronization between subprocesses, specifically by using Go waitgroups, is analyzed.
  3. Data exchange between subprocesses: Go channels

Why synchronization between subprocesses

The first article of the series exposed an example that clearly demonstrated why the subprocess synchronization is needed specifically in go:

package main

import (
  "fmt"
  "time"
)

// mySubprocess sleeps for a second
func mySubprocess() {
  fmt.Println("Entering to mySubprocess")
  time.Sleep(1 * time.Second)
  fmt.Println("Exiting from mySubprocess")
}

// main program process
func main() {
  fmt.Println("Calling mySubprocess from main()...")
  go mySubprocess()
  fmt.Println("mySubprocess finished!")
}

(Try the above code in Go Playground)

The output of the above code shows how Go forgets about their running (and scheduled) subprocesses once the main process is finished:

Calling mySubprocess from main()...
mySubprocess finished!

As seen, the main process has finished and Go has not executed mySubprocess(). Even if Go had started running it, it would have killed in in the event of the main process finishing.

Basic process syncing: Wait Groups

The Go sync package contains most of the tools used in the everyday process syncing, being the WaitGroup one of the most basic and useful tools.

As its name promises, the WaitGroup allows us pausing a process until a group of processes finish their job. Better showing it into action by fixing the problem we found in the above code:

package main

import (
  "fmt"
  "sync"
  "time"
)

// mySubprocess sleeps for a second
func mySubprocess(wg *sync.WaitGroup) {
  fmt.Println("Entering to mySubprocess")
  time.Sleep(1 * time.Second)
  fmt.Println("Exiting from mySubprocess")

  // Decrement the items in the wait group by one
  wg.Done()
}

// main program process
func main() {
  var wg sync.WaitGroup

  // Increment the items in the wait group by one
  wg.Add(1)

  fmt.Println("Calling mySubprocess from main()...")
  go mySubprocess(&wg)
  fmt.Println("mySubprocess called!")

  // Wait until the number of items 
  // in the wait group was zero
  wg.Wait()

  fmt.Println("main program finished")
}

(Try it in Go Playground)

Now the process output looks much more predictible:

Calling mySubprocess from main()...
mySubprocess called!
Entering to mySubprocess
Exiting from mySubprocess
main program finished

An improved real example

In the first article of this series we introduced an example for concurrently calculating some Fibonacci sequences. Then we used a very rudimentary way of waiting for the subprocesses to finish (calculating a more computing-costly Fibbonacci sequence), but by one program execution to other chances were of not waiting for all the sequences calculation completion.

Now, using wait groups the problem can be easily fixed, as shown in the next code example:

package main

import (
  "fmt"
  "math"
  "sync"
)

// getFibonacci calculates the Fibonacci sequence
// for a given number
func getFibonacci(n float64) float64 {
  if n <= 1 {
    return n
  }

  n2, n1: = 0.0, 1.0
  for i: = 2.0;
  i <= n;
  i++{
    n2, n1 = n1, n1 + n2
  }

  return n1
}

// mySubprocess calculates the Fibbonacci sequence
// for a given number and prints it
func printFibonacci(n float64, wg *sync.WaitGroup) {
  fmt.Printf(
    "Calculating the Fibbonacci sequence for number %.0f\n", n)
  v: = getFibonacci(n)
  fmt.Printf(
    "The Fibonacci sequence for %.0f is %.0f\n", n, v)

  wg.Done()
}

// main process
func main() {
  var wg sync.WaitGroup

  for i:=7; i >= 0; i-- {
    wg.Add(1)
    go printFibonacci(math.Pow(2, float64(i)), &wg)
  }

  wg.Wait()
}

Now the program output looks like this:

Calculating the Fibbonacci sequence for number 1
Calculating the Fibbonacci sequence for number 128
Calculating the Fibbonacci sequence for number 32
The Fibonacci sequence for 32 is 2178309
Calculating the Fibbonacci sequence for number 16
The Fibonacci sequence for 16 is 987
Calculating the Fibbonacci sequence for number 2
The Fibonacci sequence for 2 is 1
The Fibonacci sequence for 128 is 251728825683549523871268864
Calculating the Fibbonacci sequence for number 64
The Fibonacci sequence for 64 is 10610209857723
The Fibonacci sequence for 1 is 1
Calculating the Fibbonacci sequence for number 4
The Fibonacci sequence for 4 is 3
Calculating the Fibbonacci sequence for number 8
The Fibonacci sequence for 8 is 21

Comentarios

Entradas populares de este blog

Linting C# in Visual Studio Code

Though very usual in programming environments as Javascript/Typescript, linting , or analyzing code for enforcing a set of coding style rules, is not usually present in the .NET based environments. Rule enforcing is really useful when working on team shared codebases in order to keep them coherent, what in last term reduces both development times and coding errors. A linting example Maybe a practical example would be helpful for explaining what  linting  is to the newcomers (feel free to go on if you aren't). Let's imagine you are a new member in a C# development team that has well established set of coding style rules. Instead (or apart) of putting them in a document, they've adopted a tool that checks these rules during the code building process. Your first code is such ambitious as this: namespace HelloWorld {      using System;      public class Program      {           public static void Main(string[] args)           {                Console

ESlint: Ignore unused underscore variables

Some naming conventions promote the use of the underscore character (" _ ") for those variables that must be declared but are not being used. One common case is that in which a function signature contains some variables that will not be used, as for instance the Express error handlers: app.use(function(err, req, res, next) { console.error(err.stack); res.status(500).send('Something broke!'); }); In the above example only the arguments err and res are being used, though all four must be defined in the handler signature. Thus, following the naming convention of using underscores for those unused variables, we could recode it as: app.use(function(err, _, res, __) { console.error(err.stack); res.status(500).send('Something broke!'); }); Though it makes the function more readable, it comes with a problem if using ESlint: it will blame by declaring unused variables. error '_' is defined but never used error '__' is define

Using Bitbucket app passwords with git on MacOS (OSX)

Learn how Bitbucket passwords are stored by git on MacOS.