GoRoutines
Concurrent and Parallel
Creating goroutines
A simple example, time.Sleep is needed because main() ends too soon for the goroutines to print
func main() {
go sayHello()
time.Sleep(100 * time.Microsecond)
}
func sayHello() {
fmt.Println("hello")
}
hello
Goroutines can also be used in anonymous functions, msg
will use the nearest variable
func main() {
var msg = "Hello"
go func() {
fmt.Println(msg)
}()
time.Sleep(100 * time.Microsecond)
}
Hello
Race condition
The main function is executed too fast and the resource of msg is changed, so that goroutines will also be affected.
func main() {
var msg = "Hello"
go func() {
fmt.Println(msg)
}()
msg = "goodnight"
time.Sleep(100 * time.Microsecond)
}
goodnight
Synchronization
WaitGroups
Basic usage, wg.Done()
will automatically minus the counter with one, wg.Add(1)
will add one to the counter, wg.Wait()
will pause until the counter is zero
func main() {
var msg = "Hello"
wg.Add(1)
go func() {
fmt.Println(msg)
wg.Done()
}()
wg.Wait()
msg = "goodnight"
}
From the following example, although WaitGroup is useful, the execution is still a mess. The only improvement point is that the output is ordered.
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.Println(counter)
wg.Done()
}
func increment() {
counter++
wg.Done()
}
0
2
4
5
6
7
8
9
10
10
Mutexes (Lock)
Although the lock is added, because each thread is still executed, the result will still change.
var wg = sync.WaitGroup{}
var counter = 0
var m = sync.RWMutex{}
func main() {
for i := 0; i < 10; i++ {
wg.Add(2)
go sayHello()
go increment()
}
wg.Wait()
}
func sayHello() {
m.RLock()
fmt.Println(counter)
m.RUnlock()
wg.Done()
}
func increment() {
m.Lock()
counter++
m.Unlock()
wg.Done()
}
0
1
1
1
1
1
1
1
1
1
After improvement, the results are finally in order.
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.Println(counter)
m.RUnlock()
wg.Done()
}
func increment() {
counter++
m.Unlock()
wg.Done()
}
0
1
2
3
4
5
6
7
8
9
This is an exercise purely as an example, not a good exercise, because the advantages of parallelism and concurrency are gone, and the execution speed and resources are even worse.
runtime.GOMAXPROCS()
func main() {
fmt.Println(runtime.GOMAXPROCS(-1))
}
20
runtime.GOMAXPROCS(n)
is adjusted to use n threads, -1 means do not adjust and return the currently set thread nums
func main() {
runtime.GOMAXPROCS(30)
fmt.Println(runtime.GOMAXPROCS(-1))
}
30
If n = 1, it is a single thread
func main() {
runtime.GOMAXPROCS(1)
fmt.Println(runtime.GOMAXPROCS(-1))
}
Because it is a single thread, so everything is in order, after the above example is added, it will follow the order obediently. This purpose is to ensure the order is correct to avoid any race condition.
func main() {
runtime.GOMAXPROCS(1)
for i := 0; i < 10; i++ {
wg.Add(2)
go sayHello()
go increment()
}
wg.Wait()
}
func sayHello() {
fmt.Println(counter)
wg.Done()
}
func increment() {
counter++
wg.Done()
}
1
2
3
4
5
6
7
8
9
10
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 (need to use cgo, I did not open that.)