一个例子,给你讲透典型的Go并发控制

2024年 1月 10日 101.7k 0

Go中可以使用一个go关键字让程序异步执行

一个比较常见的场景:逐个异步调用多个函数,或者循环中异步调用

func main() {
 go do1()
 go do2()
 go do3()
}

// 或者

func main() {
 for i := range []int{1,2,3}{
  go do(i)
 }
}

如果了解Go并发机制,就知道main在其他goroutine运行完成之前就已经结束了,所以上面代码的运行结果是不符合预期的。我们需要使用一种叫做并发控制的手段,来保证程序正确运行

为了更容易理解,我们虚拟一个🌰

已知有一个现成的函数search,能够按照关键词执行搜索

期望实现一个新的函数coSearch能够进行批量查询

package main

import (
 "context"
 "errors"
 "fmt"
 "sync"
)

func search(ctx context.Context, word string) (string, error) {
 if word == "Go" {
  return "", errors.New("error: Go") // 模拟结果
 }
 return fmt.Sprintf("result: %s", word), nil // 模拟结果
}

func coSearch(ctx context.Context, words []string) (results []string, err error) {
 //tbd

 return
}

func main() {
 words := []string{"Go", "Rust", "PHP", "JavaScript", "Java"}
 results, err := coSearch(context.Background(), words)
 if err != nil {
  fmt.Println(err)
  return
 }

 fmt.Println(results)
}

可以先暂停想想该如何实现coSearch函数

并发控制基础

sync.WaitGroup是Go标准库中用来控制并发的结构,这里放一个使用WaitGroup实现coSearch的示例

package main

import (
 "context"
 "errors"
 "fmt"
 "sync"
)

func search(ctx context.Context, word string) (string, error) {
 if word == "Go" {
  return "", errors.New("error: Go") // 模拟结果
 }
 return fmt.Sprintf("result: %s", word), nil // 模拟结果
}

func coSearch(ctx context.Context, words []string) ([]string, error) {
 var (
  wg      = sync.WaitGroup{}
  once    = sync.Once{}
  results = make([]string, len(words))
  err     error
 )

 for i, word := range words {
  wg.Add(1)

  go func(word string, i int) {
   defer wg.Done()

   result, e := search(ctx, word)
   if e != nil {
    once.Do(func() {
     err = e
    })

    return
   }

   results[i] = result
  }(word, i)
 }

 wg.Wait()

 return results, err
}

func main() {
 words := []string{"Go", "Rust", "PHP", "JavaScript", "Java"}
 results, err := coSearch(context.Background(), words)
 if err != nil {
  fmt.Println(err)
  return
 }

 fmt.Println(results)
}

上面的代码中有非常多的细节,来逐个聊一聊

🌲 sync.WaitGroup{}并发控制

sync.WaitGroup{}的用法非常简洁

  • 当新运行一个goroutine时,我们需要调用wg.Add(1)
  • 当一个goroutine运行完成的时候,我们需要调用wg.Done()
  • wg.Wait()让程序阻塞在此处,直到所有的goroutine运行完毕。

对于coSearch来说,等待所有goroutine运行完成,也就完成了函数的任务,返回最终的结果

var (
    wg      = sync.WaitGroup{}
    //...省略其他代码
)

for i, word := range words {
    wg.Add(1)

    go func(word string, i int) {
        defer wg.Done()
  //...省略其他代码
    }(word, i)
}

wg.Wait()

🌲 for循环中的goroutine!

这是一个Go经典错误,如果goroutine中使用了for迭代的变量,所有goroutine都会获得最后一次循环的值。例如下面的示例,并不会输出"a", "b", "c" 而是输出 "c", "c", "c"

func main() {
done := make(chan bool)

values := []string{"a", "b", "c"}
for _, v := range values {
go func() {
fmt.Println(v)
done

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论