MST

星途 面试题库

面试题:Go的WaitGroup在复杂并发场景下的应用与陷阱

假设有一个复杂的并发场景,多个协程需要并发执行一系列任务,这些任务之间存在依赖关系,且部分任务执行结果会影响后续任务的执行逻辑。请使用WaitGroup设计一个解决方案,并指出在这个过程中可能遇到的并发安全陷阱以及如何避免。
29.1万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

使用WaitGroup设计解决方案

以下是一个使用Go语言和WaitGroup来解决该问题的示例代码,假设使用Go语言进行开发:

package main

import (
    "fmt"
    "sync"
)

// 定义任务函数
func task1(wg *sync.WaitGroup, resultChan chan int) {
    defer wg.Done()
    // 模拟任务1的执行
    result := 10
    resultChan <- result
}

func task2(wg *sync.WaitGroup, resultChan chan int, depResult int) {
    defer wg.Done()
    // 依赖task1的结果
    newResult := depResult + 5
    resultChan <- newResult
}

func main() {
    var wg sync.WaitGroup
    resultChan1 := make(chan int)
    resultChan2 := make(chan int)

    // 启动任务1
    wg.Add(1)
    go task1(&wg, resultChan1)

    // 等待任务1完成并获取结果
    go func() {
        wg.Wait()
        close(resultChan1)
        close(resultChan2)
    }()

    depResult := <-resultChan1
    // 启动任务2,依赖任务1的结果
    wg.Add(1)
    go task2(&wg, resultChan2, depResult)

    // 获取任务2的结果
    finalResult := <-resultChan2
    fmt.Printf("Final result: %d\n", finalResult)
}

并发安全陷阱及避免方法

  1. 未正确使用WaitGroup
    • 陷阱:忘记调用wg.Add增加计数,或者在任务结束前未调用wg.Done减少计数,会导致wg.Wait永远阻塞或提前返回。
    • 避免方法:确保在启动每个协程前调用wg.Add(1),并且在每个协程的函数中使用defer wg.Done()来保证函数结束时计数减少。
  2. 数据竞争
    • 陷阱:多个协程同时访问和修改共享变量时可能发生数据竞争,例如在任务函数中共享一个变量并同时读写。
    • 避免方法:使用互斥锁(sync.Mutex)来保护共享变量,或者使用通道(chan)进行数据传递,避免直接共享数据。
  3. 通道操作不当
    • 陷阱:如果通道未正确关闭,可能会导致接收操作永远阻塞。另外,如果向已关闭的通道发送数据,会导致运行时恐慌。
    • 避免方法:在所有发送操作完成后关闭通道,并且在接收端使用for - range循环或者ok-idiom(value, ok := <-chan)来处理通道关闭的情况。