go通过channel获取goroutine的处理结果

一、前言

      前几天写了篇文章,是通过sync.Map获取goroutine的返回结果然后做出处理,但是一直感觉方案一般,不是很好。毕竟channel才是钦定的太子,所以还是用channel好一些。

golang控制goroutine数量以及获取处理结果

二、误区以及实战代码

1、误区

      博主自己用channel一般都是用来控制goroutine的并发,所以channel结构比较简单,就想当然的认为channel只适合存储简单的结构,复杂的函数处理结果通过channel处理不太方便,是在是谬之千里

2.实战代码

注:以下为脱敏的伪代码

代码含义概览:
(1)通过channel控制goroutine数量
(2)定义channel结构体,结构体里面可以根据需求嵌套其他结构体,实现我们想要的复杂结构
(3)每次循环获取go函数处理结果,处理每次循环结果
//EsBulkDataRes 可以定义复杂的结构,或者嵌套结构体
type EsBulkDataRes struct {
	SucceededNums int    `json:"succeeded_nums"`
	FailedNums    int    `json:"failed_nums"`
	ErrData       string `json:"err_data"`
	//TestDoc *TestDoc
}

type TestDoc struct {
	ID     string                 `json:"_id,omitempty"`
	Source map[string]interface{} `json:"_source,omitempty"` //map里面可以存储复杂的结构
}

func testChannel() {
	//定义要获取的返回值,成功数量,失败数量,失败id集合
	succeededNums, failedNums := 0, 0
	var errData string
	DealRes := make(chan EsBulkDataRes)
	defer func() {
		close(DealRes)
	}()
	wg := sync.WaitGroup{}
	//控制goroutine数量,保证同时只有10个goroutine
	chan1 := make(chan struct{}, 10)
	ctx := context.Background()
	for {
		//业务逻辑
		// ....
		//goroutine加速,chan1写满,则阻塞。等待之前的goroutine释放才能继续循环
		chan1 <- struct{}{}
		wg.Add(1)
		go func(ctx context.Context, targetIndexName string, esClient *elastic.Client) {
			defer func() {
				if err1 := recover(); err1 != nil { //产生了panic异常
					log.Errorf(ctx, "%s go panic! err:(+%v)", logPreFix, err1)
				}
				//执行完毕再释放channel
				<-chan1
				return
			}()
			bulkRequest := esClient.Bulk()
			//ES使用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
			}
			//执行结果写入channel
			DealRes <- *bulkResByAssetInfo
			//执行完毕再释放channel
			<-chan1
		}(ctx, targetIndexName, esClient)
		//goroutine 执行结束,读取channel累加结果
		//读取channel,也是为了方便下一个 goroutine 的写入。没读的话,会阻塞
		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函数都依赖于上一步的处理结果,如何实现串行。此处给出伪代码,参考下即可。

//定义channel结构
type chanStruct struct {
	Res1 int64
}

//每个函数中重新给channel赋值
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
}

//https://segmentfault.com/q/1010000041024462/
//无缓冲通道读写都必须在协程里,否则会阻塞。有缓冲通道则可以不需要都准备好,读或者写可以写在当前线程里而不会阻塞。
func main() {
	chan0 := make(chan chanStruct, 1)
	//这里使用了"golang.org/x/sync/errgroup" 这个包,博主个人实验,在此处非必需
	g, ctx := errgroup.WithContext(context.Background())
	chan0 <- chanStruct{
		Res1: 1,
	}
	fmt.Println("write chan success!")
	//errgroup控制并发,并获取goroutine返回的错误(此处没用到)
	g.Go(func() error {
		for {
			select {
			//注意这里,由于每次我们都读出来了channel,因此需要在函数中给channel赋值
			//保证能触发下一个函数
			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()
			}
		}

	})
	//errgroup的Wait类似于sync.withGroup的Wait()方法,等待goroutine执行结束
	if err := g.Wait(); err != nil {
		log.Fatal(err)
	}

}

四、后记

      我们该如何确认自己写的代码比较好呢?这个好又要如何定义?只是实现功能还是说要保持优雅? 以上是博主跟一个大佬聊天的时候大佬问的问题。

      对于我们开发者来说,以实现需求为第一目的是绝对没问题的,但是代码质量也需要持续提升。怎么提升呢,当然是看大佬的代码!哪里大佬的代码最多呢,当然是github!

      博主最近看了https://github.com/olivere/esdiff 大佬的代码,才发现自己以前的狭隘,也惊叹于大佬的写法之妙。这还只是个不知名的开源项目,不知道k8s,etcd等知名项目又会是怎样的波澜壮阔!加油!

end

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铁柱同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值