bg

Go concurrency


Posted on June 23, 2023
Tricks
Go

Getting Started

In Go you can use a goKeywords make the program execute asynchronously

A more common scenario: calling multiple functions asynchronously one by one, or calling them asynchronously in a loop

test.go

_22
_22
func main() {
_22
_22
go do1()
_22
_22
go do2()
_22
_22
go do3()
_22
_22
}
_22
_22
// 或者
_22
_22
func main() {
_22
_22
for i := range []int{1,2,3}{
_22
_22
go do(i)
_22
_22
}
_22
_22
}

If you understand the Go concurrency mechanism, you will know mainIt has ended before other goroutines have finished running, so the results of the above code are not as expected. We need to use a method called concurrency control to ensure that the program runs correctly

for example:

It is known that there is a ready-made function search, able to perform searches based on keywords

Expect to implement a new function coSearchAbility to perform batch queries


_63
package main
_63
_63
_63
_63
import (
_63
_63
"context"
_63
_63
"errors"
_63
_63
"fmt"
_63
_63
"sync"
_63
_63
)
_63
_63
_63
_63
func search(ctx context.Context, word string) (string, error) {
_63
_63
if word == "Go" {
_63
_63
return "", errors.New("error: Go") // 模拟结果
_63
_63
}
_63
_63
return fmt.Sprintf("result: %s", word), nil // 模拟结果
_63
_63
}
_63
_63
_63
_63
func coSearch(ctx context.Context, words []string) (results []string, err error) {
_63
_63
//tbd
_63
_63
_63
_63
return
_63
_63
}
_63
_63
_63
_63
func main() {
_63
_63
words := []string{"Go", "Rust", "PHP", "JavaScript", "Java"}
_63
_63
results, err := coSearch(context.Background(), words)
_63
_63
if err != nil {
_63
_63
fmt.Println(err)
_63
_63
return
_63
_63
}
_63
_63
_63
_63
fmt.Println(results)
_63
_63
}

Control basics

sync.WaitGroupIt is a structure used to control concurrency in the Go standard library. Here is one for use WaitGroupaccomplish coSearchExample of


_116
_116
package main
_116
_116
_116
_116
import (
_116
_116
"context"
_116
_116
"errors"
_116
_116
"fmt"
_116
_116
"sync"
_116
_116
)
_116
_116
_116
_116
func search(ctx context.Context, word string) (string, error) {
_116
_116
if word == "Go" {
_116
_116
return "", errors.New("error: Go") // 模拟结果
_116
_116
}
_116
_116
return fmt.Sprintf("result: %s", word), nil // 模拟结果
_116
_116
}
_116
_116
_116
_116
func coSearch(ctx context.Context, words []string) ([]string, error) {
_116
_116
var (
_116
_116
wg = sync.WaitGroup{}
_116
_116
once = sync.Once{}
_116
_116
results = make([]string, len(words))
_116
_116
err error
_116
_116
)
_116
_116
_116
_116
for i, word := range words {
_116
_116
wg.Add(1)
_116
_116
_116
_116
go func(word string, i int) {
_116
_116
defer wg.Done()
_116
_116
_116
_116
result, e := search(ctx, word)
_116
_116
if e != nil {
_116
_116
once.Do(func() {
_116
_116
err = e
_116
_116
})
_116
_116
_116
_116
return
_116
_116
}
_116
_116
_116
_116
results[i] = result
_116
_116
}(word, i)
_116
_116
}
_116
_116
_116
_116
wg.Wait()
_116
_116
_116
_116
return results, err
_116
_116
}
_116
_116
_116
_116
func main() {
_116
_116
words := []string{"Go", "Rust", "PHP", "JavaScript", "Java"}
_116
_116
results, err := coSearch(context.Background(), words)
_116
_116
if err != nil {
_116
_116
fmt.Println(err)
_116
_116
return
_116
_116
}
_116
_116
_116
_116
fmt.Println(results)
_116
_116
}

There are a lot of details in the above code, let’s talk about them one by one. sync.WaitGroupConcurrency control

sync.WaitGroup{} The usage is very simple

-When a new goroutine is run, we need to call wg.Add(1)

-When a goroutine is finished running, we need to call wg.Done()

-wg.Wait()Let the program block here until all goroutines have finished running.

for coSearchIn other words, waiting for all goroutines to complete, thus completing the task of the function and returning the final result.


_29
var (
_29
_29
wg = sync.WaitGroup{}
_29
_29
//...省略其他代码
_29
_29
)
_29
_29
_29
_29
for i, word := range words {
_29
_29
wg.Add(1)
_29
_29
_29
_29
go func(word string, i int) {
_29
_29
defer wg.Done()
_29
_29
//...省略其他代码
_29
_29
}(word, i)
_29
_29
}
_29
_29
_29
_29
wg.Wait()

forgoroutine in loop

This is a classic Go error, if used in goroutine forIterative variables, all goroutines will get the value of the last loop. For example, the following example does not output "a", "b", "c" but outputs "c", "c", "c"


_32
_32
func main() {
_32
_32
done := make(chan bool)
_32
_32
_32
_32
values := []string{"a", "b", "c"}
_32
_32
for _, v := range values {
_32
_32
go func() {
_32
_32
fmt.Println(v)
_32
_32
done <- true
_32
_32
}()
_32
_32
}
_32
_32
_32
_32
// wait for all goroutines to complete before exiting
_32
_32
for _ = range values {
_32
_32
<-done
_32
_32
}
_32
_32
}

© Ari1009. All rights reserved.