2025年4月

场景1:实现并发调用多个方法,有一个返回结果就都返回
思路:可以使用 select 语句结合通道来等待多个 goroutine 返回结果,一旦有 goroutine 返回结果,就立即处理该结果并结束程序。

/**
 * @desc
 * @date 2025/4/19
 * @user yangshuo
 */

package main

import (
    "context"
    "fmt"
    "math/rand"
    "sync"
    "time"
)

func mysql(ctx context.Context, ch chan<- string, wg *sync.WaitGroup) {
    defer wg.Done()
    d := time.Duration(rand.Intn(3)+1) * time.Second

    t := time.NewTimer(d)
    defer t.Stop()

    select {
    case <-ctx.Done():
        return
    case <-t.C:
    }

    select {
    case <-ctx.Done():
        return
    case ch <- "mysql":
    }
}

func redis(ctx context.Context, ch chan<- string, wg *sync.WaitGroup) {
    defer wg.Done()
    d := time.Duration(rand.Intn(3)+1) * time.Second
    t := time.NewTimer(d)
    defer t.Stop()

    select {
    case <-ctx.Done():
        return
    case <-t.C:
    }

    select {
    case <-ctx.Done():
        return
    case ch <- "redis":
    }
}

func httpReq(ctx context.Context, ch chan<- string, wg *sync.WaitGroup) {
    defer wg.Done()
    d := time.Duration(rand.Intn(3)+1) * time.Second
    t := time.NewTimer(d)
    defer t.Stop()

    select {
    case <-ctx.Done():
        return
    case <-t.C:
    }

    select {
    case <-ctx.Done():
        return
    case ch <- "http":
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // 缓冲=1,避免第一个发送方被无谓阻塞
    resChan := make(chan string, 1)

    var wg sync.WaitGroup
    wg.Add(3)
    go mysql(ctx, resChan, &wg)
    go redis(ctx, resChan, &wg)
    go httpReq(ctx, resChan, &wg)

    // 只拿第一个结果就取消其他 goroutine
    select {
    case res := <-resChan:
        fmt.Println(res)
        cancel()
    case <-time.After(5 * time.Second):
        t.Fatal("timeout")
    }

    wg.Wait()
    // 不需要关闭 resChan(这里多发送者、且我们拿到一个结果就结束)

}

场景2:实现并发调用多个校验方法,有一个成功就返回成功,所有方法都失败就返回失败。
思路:与场景1不同的是,失败情况需要等待所有goroutine都执行完。

/**
 * @desc
 * @date 2025/4/20
 * @user yangshuo
 */

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func validateFunc1() bool {
    // 模拟验证逻辑
    // 设置随机种子
    return false
    rand.Seed(time.Now().UnixNano())

    // 生成随机数(0 或 1)
    randomNum := rand.Intn(2)

    // 根据随机数返回 true 或 false
    result := randomNum == 1
    fmt.Println(result)
    return result
}

func validateFunc2() bool {
    return false
    // 设置随机种子
    rand.Seed(time.Now().UnixNano())

    // 生成随机数(0 或 1)
    randomNum := rand.Intn(2)

    // 根据随机数返回 true 或 false
    result := randomNum == 1
    fmt.Println(result)
    return result
}

func validateFunc3() bool {
    return false
    // 模拟验证逻辑
    // 设置随机种子
    rand.Seed(time.Now().UnixNano())

    // 生成随机数(0 或 1)
    randomNum := rand.Intn(2)

    // 根据随机数返回 true 或 false
    result := randomNum == 1
    fmt.Println(result)
    return result
}

func main() {
    // 定义验证函数
    validateFuncs := []func() bool{validateFunc1, validateFunc2, validateFunc3}
    // 使用 channel 通知是否有函数通过验证
    passed := make(chan bool)

    // 并发调用验证函数
    for _, vf := range validateFuncs {
        go func(fn func() bool) {
            if fn() {
                passed <- true
            }
        }(vf)
    }

    // 检查是否有函数通过验证
    for range validateFuncs {
        select {
        case <-passed:
            fmt.Println("Validation passed")
            return
    }

    fmt.Println("Validation failed")
}

需要注意的是:当所有 goroutines 都未通过验证时,由于没有 goroutine 向 passed 通道发送消息,select 语句会一直阻塞等待消息,从而导致程序发生死锁。为了避免这种情况,可以加上default分支。

// 检查是否有函数通过验证
    for range validateFuncs {
        select {
        case <-passed:
            fmt.Println("Validation passed")
            return
        default:

        }
    }