一、前言
前几天写了篇文章,是通过sync.Map
获取goroutine
的返回结果然后做出处理,但是一直感觉方案一般,不是很好。毕竟channel
才是钦定的太子,所以还是用channel
好一些。
golang控制goroutine数量以及获取处理结果
二、误区以及实战代码
1、误区
博主自己用channel
一般都是用来控制goroutine
的并发,所以channel
结构比较简单,就想当然的认为channel
只适合存储简单的结构,复杂的函数处理结果通过channel
处理不太方便,是在是谬之千里。
2.实战代码
注:以下为脱敏的伪代码
代码含义概览:
(1)通过channel控制goroutine数量
(2)定义channel结构体,结构体里面可以根据需求嵌套其他结构体,实现我们想要的复杂结构
(3)每次循环获取go函数处理结果,处理每次循环结果
type EsBulkDataRes struct {
SucceededNums int `json:"succeeded_nums"`
FailedNums int `json:"failed_nums"`
ErrData string `json:"err_data"`
}
type TestDoc struct {
ID string `json:"_id,omitempty"`
Source map[string]interface{} `json:"_source,omitempty"`
}
func testChannel() {
succeededNums, failedNums := 0, 0
var errData string
DealRes := make(chan EsBulkDataRes)
defer func() {
close(DealRes)
}()
wg := sync.WaitGroup{}
chan1 := make(chan struct{}, 10)
ctx := context.Background()
for {
chan1 <- struct{}{}
wg.Add(1)
go func(ctx context.Context, targetIndexName string, esClient *elastic.Client) {
defer func() {
if err1 := recover(); err1 != nil {
log.Errorf(ctx, "%s go panic! err:(+%v)", logPreFix, err1)
}
<-chan1
return
}()
bulkRequest := esClient.Bulk()
bulkResByAssetInfo := new(EsBulkDataRes)
bulkResByAssetInfo, err = BulkEsDataByAssetInfo(ctx, bulkRequest, targetIndexName)
if err != nil {
log.Errorf(ctx, "%s BulkEsDataByAssetInfo error (%+v) ,test:(+%v)", logPreFix, err, string_util.TransferToString(fileUnitList))
return
}
DealRes <- *bulkResByAssetInfo
<-chan1
}(ctx, targetIndexName, esClient)
select {
case d, ok := <-DealRes:
if !ok {
continue
}
succeededNums += d.SucceededNums
failedNums += d.FailedNums
errData += d.ErrData
case <-ctx.Done():
return
}
}
wg.Wait()
fmt.Println("成功数量:", succeededNums)
fmt.Println("失败数量:", failedNums)
fmt.Println("失败id:", errData)
}
三、channel控制多个goroutine串行
这部分是博主之前碰到的面试题,说多个go
函数,每个go
函数都依赖于上一步的处理结果,如何实现串行。此处给出伪代码,参考下即可。
type chanStruct struct {
Res1 int64
}
func test1(chan1 chan chanStruct) {
res1 := new(chanStruct)
fmt.Println("test1")
res1.Res1 = 2
chan1 <- *res1
return
}
func test2(chan2 chan chanStruct) {
res2 := new(chanStruct)
fmt.Println("test2")
res2.Res1 = 3
chan2 <- *res2
return
}
func test3(chan3 chan chanStruct) {
fmt.Printf("test3,chanStruct:(%+v)", chan3)
return
}
func main() {
chan0 := make(chan chanStruct, 1)
g, ctx := errgroup.WithContext(context.Background())
chan0 <- chanStruct{
Res1: 1,
}
fmt.Println("write chan success!")
g.Go(func() error {
for {
select {
case d, ok := <-chan0:
fmt.Println("d:", d)
if ok {
if d.Res1 == 1 {
go test1(chan0)
} else if d.Res1 == 2 {
go test2(chan0)
} else if d.Res1 == 3 {
go test3(chan0)
fmt.Println("end")
return nil
}
}
case <-ctx.Done():
return ctx.Err()
}
}
})
if err := g.Wait(); err != nil {
log.Fatal(err)
}
}
四、后记
我们该如何确认自己写的代码比较好呢?这个好又要如何定义?只是实现功能还是说要保持优雅? 以上是博主跟一个大佬聊天的时候大佬问的问题。
对于我们开发者来说,以实现需求为第一目的是绝对没问题的,但是代码质量也需要持续提升。怎么提升呢,当然是看大佬的代码!哪里大佬的代码最多呢,当然是github!
博主最近看了https://github.com/olivere/esdiff
大佬的代码,才发现自己以前的狭隘,也惊叹于大佬的写法之妙。这还只是个不知名的开源项目,不知道k8s,etcd等知名项目又会是怎样的波澜壮阔!加油!
end