go并发控制--WaitGroup 和 Select-case

学习博客:https://www.jianshu.com/p/6232a9a32230

Q:假如现在需要实现一个API,该API会执行多个 python 脚本,并根据爬取结果进行数据处理。
A:比较容易想到我们需要使用go并发控制,以下是两种处理方式

使用 waitGroup

tasks := []taskStruct{taskA, taskB, taskC}
var wg sync.WaitGroup

wg.Add(len(tasks))

res := []taskResult{}

for i := 0; i < len(tasks); i++ {
    go func(id uint) {
        defer wg.Done()
        r, err := executePythonScript(id)
        if err != nil {
			// log and do something
            return
        }
        res := append(res, r)
    }(tasks[i].id)
}

wg.Wait()

for i := range res {
  // deal with result
}

使用 Select-case

tasks := []taskStruct{taskA, taskB, taskC}

ch := make(chan taskResult)

for i := 0; i < len(tasks); i++ {
    go func(id uint) {
        res, err := executePythonScript(id)
        if err != nil {
            // log and return
            return
        }
        ch <- taskResult{
            id: res.id,
            content: res.content,
            errors: res.errors,
        }
    }(tasks[i].id)
}

for i := 0; i < len(tasks); i++ {
    select {
    case t := <- ch:
        // do something 
    case <- time.After(5 * time.Minute):
        // do something
    }
}

对比

流程比较

使用 waitGroup :等待所有待执行的脚本全部执行完后,再对每一个结果进行其他操作
使用 Select-case :程序先是被 for-loop 里的 select-case 所阻塞(读写值为nil的channel),
当协程里每执行完一个脚本得到结果后就会向 channel 中写入数据,
for-loop 里的 select-case 读到 channel 中的值进行相关操作且进入下一层循环。

线程安全

使用 WaitGroup :易于实现,即每一个子协程执行完脚本后都会向 slice 中添加结果,
当所有子协程结束后在进行操作。
使用 slice、map等共享内存的数据结构来存储每一个协程的处理结果,不是线程安全的。
使用 Select-case :使用 Channel 传递数据,即每个子协程得到结果后通过 channel 向主协程传递结果,
并且保证了线程安全。

Go 语言中最常见的、也是经常被人提及的设计模式就是:
不要通过共享内存的方式进行通信,而是应该通过通信的方式共享内存。

优劣对比

WaitGroup适合等待一系列子协程全部完成操作。
Select-case + channel 比较适合无需关注全部子协程的状态,
当某个子协程向 channel 中写入数据时,就可以对其进行处理。

おすすめ

転載: blog.csdn.net/wangkai6666/article/details/121180956