6. Concurrency in Go
1. Making threads (goroutines) lightweight and easy to create
2. Using explicit messaging (via channels) to communicate across
threads
7. Beliefs about Go:
● Make concurrent programming easier and less
error-prone
● Make heavy use of message passing via channels,
which is less error prone than shared memory
● Have less concurrency bugs
● Built-in deadlock and data racing can catch any bugs
8. Go Concurrency Usage Patterns
surprising finding is that shared memory synchronisation operations are still
used more often than message passing
10. Go Concurrency Bugs
1. Blocking - one or more goroutines are unintentionally stuck in their execution
and cannot move forward.
2. Non-Blocking - If instead all goroutines can finish their tasks but their
behaviors are not desired, we call them non-blocking ones
16. func finishReq(timeout time.Duration) r ob {
ch := make(chanob)
go func() {
result := fn()
ch <- result
}()
select {
case result = <- ch
return result
case <- time.After(timeout)
return nil
}
}
Blocking Bug caused by Channel
17. func finishReq(timeout time.Duration) r ob {
ch := make(chanob, 1)
go func() {
result := fn()
ch <- result // blocking
}()
select {
case result = <- ch
return result
case <- time.After(timeout)
return nil
}
}
Blocking Bug caused by Channel
20. Non-Blocking Bugs Causes
There are much fewer non-blocking bugs caused by message passing than by
shared memory accesses.
21. Non-Blocking Bug caused by select and channel
ticker := time.NewTicker()
for {
f()
select {
case <- stopCh
return
case <- ticker
}
}
22. Non-Blocking Bug caused by select and channel
ticker := time.NewTicker()
for {
select{
case <- stopCh:
return
default:
}
f()
select {
case <- stopCh:
return
case <- ticker:
}
}
23. Non-Blocking Bug caused Timer
timer := time.NewTimer(0)
if dur > 0 {
timer = time.NewTimer(dur)
}
select{
case <- timer.C:
case <- ctx.Done:
return nil
}
24. Non-Blocking Bug caused Timer
timer := time.NewTimer(0)
var timeout <- chan time.Time
if dur > 0 {
timer = time.NewTimer(dur)
timeout = time.NewTimer(dur).C
}
select{
case <- timer.C:
case <- timeout:
case <- ctx.Done:
return nil
}
25. A data race caused by anonymous function
for i:=17; i<=21; i++ { // write
go func() {
apiVersion := fmt.Sprintf(“v1.%d”, i)
}()
}
26. A data race caused by anonymous function
for i:=17; i<=21; i++ { // write
go func(i int) {
apiVersion := fmt.Sprintf(“v1.%d”, i)
}(i)
}
27. A data race caused by passing reference through channel
28. Conclusion
1. Contrary to the common belief that message passing is less
error-prone, more blocking bugs in our studied Go applications
are caused by wrong message passing than by wrong shared
memory protection.
2. Message passing causes less nonblocking bugs than shared
memory synchronization
3. Misusing Go libraries can cause both blocking and
nonblocking bugs