golang 并发编程,每日一题
题目
有三个需要并发执行的函数,三个函数会返回同一类型的值,
且三个函数执行时间在2ms到10ms之间不等,而主程序要求在5ms内返回结果,
若5ms内没有执行完毕,则强制返回结果,这个时候某个函数可能还没有返回因此没有值。
func searchText(query string) ResultText
func searchImage(query string) ResultText
func searchVideo(query string) ResultText
代码
要点在注释中备注
package test
import (
"context"
"fmt"
"math/rand"
"testing"
"time"
)
type ResultText string
type ResultImage string
type ResultVideo string
type AllRes struct {
ResultText
ResultImage
ResultVideo
}
func TestConcur(t *testing.T) {
ctx, cancelFun := context.WithTimeout(context.Background(), time.Millisecond*5)
defer cancelFun()
textCh := make(chan ResultText)
imageCh := make(chan ResultImage)
videoCh := make(chan ResultVideo)
go searchText(ctx, "text", textCh)
go searchImage(ctx, "image", imageCh)
go searchVideo(ctx, "video", videoCh)
res := AllRes{}
for {
select {
case textRes := <-textCh:
res.ResultText = textRes
case imageRes := <-imageCh:
res.ResultImage = imageRes
case videoRes := <-videoCh:
res.ResultVideo = videoRes
case <-ctx.Done():
fmt.Println(res)
return // 实际业务中,应该在这里返回数据
}
}
}
func searchText(ctx context.Context, query string, ch chan ResultText) ResultText {
res := ResultText("")
select {
case <-ctx.Done():
fmt.Errorf("超时")
ch <- ""
return res
default:
// 耗时操作,业务逻辑,实际业务逻辑应该封装为一个函数,不写在这个部分,这个部分主要做超时控制
duration := time.Duration(rand.Intn(9) + 2)
time.Sleep(time.Millisecond * duration)
res = ResultText(query + duration.String())
ch <- res
}
return res
}
func searchImage(ctx context.Context, query string, ch chan ResultImage) ResultImage {
res := ResultImage("")
select {
case <-ctx.Done():
fmt.Errorf("超时")
ch <- ""
return res
default:
// 耗时操作,业务逻辑,实际业务逻辑应该封装为一个函数,不写在这个部分,这个部分主要做超时控制
duration := time.Duration(rand.Intn(9) + 2)
time.Sleep(time.Millisecond * duration)
res = ResultImage(query + duration.String())
ch <- res
}
return res
}
func searchVideo(ctx context.Context, query string, ch chan ResultVideo) ResultVideo {
res := ResultVideo("")
select {
case <-ctx.Done():
fmt.Errorf("超时")
ch <- ""
return res
default:
// 耗时操作,业务逻辑,实际业务逻辑应该封装为一个函数,不写在这个部分,这个部分主要做超时控制
duration := time.Duration(rand.Intn(9) + 2)
time.Sleep(time.Millisecond * duration)
res = ResultVideo(query + duration.String())
ch <- res
}
return res
}
代码CR优化:
-
避免重复代码
searchText、searchImage 和 searchVideo 函数中的代码几乎完全相同,除了类型不同。可以将公共逻辑提取成一个通用的函数,减少代码重复,提高可维护性。 -
错误处理
fmt.Errorf(“超时”) 在 select 中并没有保存错误信息,只是打印出来。由于 select 中的 ctx.Done() 通道已经处理了超时信号,可以直接返回并结束函数,不需要再创建额外的错误信息。 -
通道关闭
searchText、searchImage、searchVideo 中,错误情况下会向通道发送一个空字符串 ch <- “”,但这样会让调用方(TestConcur)收到一个无效的结果。更好的方式是确保只有有效的结果才通过通道发送,或者使用 select 返回一个默认值而不是空字符串。 -
随机时间的生成
随机时间 rand.Intn(9) + 2 的生成是硬编码的,建议将这些常量值提取为变量或常量,以便后期修改。 -
AllRes 中的结果字段赋值
在 TestConcur 中,select 中的每个分支直接赋值给 res.ResultText、res.ResultImage 和 res.ResultVideo,这样会导致一旦有一个结果返回,其他通道可能会被阻塞。应该考虑提前判断超时逻辑,避免在业务处理逻辑中阻塞。 -
上下文取消和超时
context 的取消机制已经处理得很好,但为了提高代码可读性和灵活性,可以把超时设置提取为常量,而不是直接硬编码。
package main
import (
"context"
"fmt"
"math/rand"
"testing"
"time"
)
type ResultText string
type ResultImage string
type ResultVideo string
type AllRes struct {
ResultText
ResultImage
ResultVideo
}
const maxDuration = 10 // 用于生成随机等待时间的常量
func TestConcur(t *testing.T) {
ctx, cancelFun := context.WithTimeout(context.Background(), time.Millisecond*5)
defer cancelFun()
// 创建通道
textCh := make(chan ResultText)
imageCh := make(chan ResultImage)
videoCh := make(chan ResultVideo)
// 启动并发任务
go search(ctx, "text", textCh)
go search(ctx, "image", imageCh)
go search(ctx, "video", videoCh)
// 存储结果
res := AllRes{}
for {
select {
case textRes := <-textCh:
res.ResultText = textRes
case imageRes := <-imageCh:
res.ResultImage = imageRes
case videoRes := <-videoCh:
res.ResultVideo = videoRes
case <-ctx.Done():
fmt.Println(res) // 输出结果
return // 退出,实际业务中可以返回数据
}
}
}
// 通用的搜索函数
func search(ctx context.Context, query string, ch interface{}) {
// 随机生成操作耗时
duration := time.Duration(rand.Intn(maxDuration) + 2)
select {
case <-ctx.Done(): // 超时退出
fmt.Println("超时:", query)
return
default :
// 根据 query 决定返回的结果类型
switch v := ch.(type) {
case chan ResultText:
v <- ResultText(query + duration.String())
case chan ResultImage:
v <- ResultImage(query + duration.String())
case chan ResultVideo:
v <- ResultVideo(query + duration.String())
}
}
}