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

猜你喜欢

转载自blog.csdn.net/LJFPHP/article/details/125963501