Goroutines

 This is a study note for YouTube tutorial: https://www.youtube.com/watch?v=YS4e4q9oBaU&t=20036s

Goroutines are green threads. Green threads are created and managed by applications instead of kernel like the native threads. One of the benefits in green thread is that it can be created very fast.

Creating goroutines

Use keyword "go" before the invocation of the function to turn normal functions into goroutines.
func main() {
go sayHello()

}

func sayHello() {
fmt.Println("Hello")
}
In many cases, we may need anonymous functions to run goroutines.
func main() {
var msg = "Hello"
go func() {
fmt.Println(msg)
}()
time.Sleep(100 * time.Millisecond)

}
The result is:
Hello

From above example, you can see that goroutines are able to use the variable in the outer space. This is handled by go runtime. However this is not recommended to use, because later when you change the variable, it could impact the behaviour in goroutine. Instead, you can function parameter to pass value, in this case the variables in upper block will not impact the goroutine, because the value is duplicated in goroutine.
func main() {
var msg = "Hello"
go func(msg string) {
fmt.Println(msg)
}(msg)
msg = "Goodbye"
time.Sleep(100 * time.Millisecond)

}
The result is:
Hello


Synchronization

You may notice the sleep function call in main function is not really good approach, because it really slows down the program. So we can use WaitGroup to solve this problem.

WaitGroups

var wg = sync.WaitGroup{}

func main() {
var msg = "Hello"
wg.Add(1)
go func(msg string) {
fmt.Println(msg)
wg.Done() // decrement the wait group by 1
}(msg)
msg = "Goodbye"
wg.Wait() // wait until wait group be 0

}

To illustrate further, below is slightly more complicated example that uses two goroutine.

var wg = sync.WaitGroup{}
var counter = 0

func main() {
for i := 0; i < 10; i++ {
wg.Add(2)
go sayHello()
go increment()
}
wg.Wait()

}

func sayHello() {
fmt.Printf("Hello #%v\n", counter)
wg.Done()
}

func increment() {
counter++
wg.Done()
}

The result is:
Hello #3 Hello #1 Hello #7 Hello #8 Hello #7 Hello #0 Hello #7 Hello #3 Hello #0 Hello #10

Mutexes

From above result, you see the number is not in sequence. In order to do that, you need to use mutex to lock the shared variables between goroutines.

var wg = sync.WaitGroup{}
var counter = 0
var m = sync.RWMutex{}

func main() {
for i := 0; i < 10; i++ {
wg.Add(2)
m.RLock()
go sayHello()
m.Lock()
go increment()
}
wg.Wait()

}

func sayHello() {
fmt.Printf("Hello #%v\n", counter)
m.RUnlock()
wg.Done()
}

func increment() {
counter++
m.Unlock()
wg.Done()
}

Actually this example has no sense, because it is totally remove the multi threading advantages.

Parallelism

You can use  "runtime.GOMAXPROCS(int)" function to set the number of low-level threads you want to use. If you set the parameter to be -1, the function return the thread number you set or return the number of cores by default if you didn't set any.

func main() {
fmt.Printf("Threads: %v\n", runtime.GOMAXPROCS(-1))
runtime.GOMAXPROCS(1)
fmt.Printf("Threads: %v\n", runtime.GOMAXPROCS(-1))

}

Threads: 8 Threads: 1

The number is better to use the number of cores, because if you set a big value, you could slow down the performance.

Best practices

  • Don't create goroutines in libraries
    • Let consumer control concurrency
  • When creating a goroutine, know how it will end
    • Avoids subtle memory leaks
  • Check for race conditions at compile time -  go run -race main.go 


References: https://www.youtube.com/watch?v=YS4e4q9oBaU&t=20036s

Comments

Popular posts from this blog

Basic understanding of TLS-PSK protocol

Differences between ASIC, ASSP and ASIP

Orthogonal instruction set