select和channel
场景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:
}
}
2025年10月新盘 做第一批吃螃蟹的人