2021-05-16

本文解析了Go语言中并发任务的巧妙运用,通过`makeThumbnails`函数讲解了无缓冲通道如何避免goroutine阻塞,关键在于closer goroutine的执行顺序与channel操作的同步。
摘要由CSDN通过智能技术生成

最近在学习Go语言的特性,看到一个并发的例子特别有意思,在此记录下

func makeThumbnails(filenames <-chan string) int64 {
	sizes := make(chan int64)
	var wg sync.WaitGroup // number of working goroutines
	for f := range filenames {
		wg.Add(1) //Add, which increments the counter, must be called before the worker goroutine starts, not within it;
		// worker
		go func(f string) {
            // go func里的defer是要等到最后一行代码(哪怕最后一行代码是异常)执行过后才能执行而最后一行代码恰恰是一个阻塞代码,因为通道不是缓冲通道
			defer wg.Done() 
			thumb, err := thumbnail.ImageFile(f)
			if err != nil {
				log.Println(err)
				return
			}
			info, _ := os.Stat(thumb) // OK to ignore error
			sizes <- info.Size() //这个操作会阻塞,因为是无缓冲通道(应该说这里的操作和下面的range是交错进行的,中间的close是在最后插入执行)
		}(f)
	}

    // 这两个操作只能放在单独的goroutine里 不能放在主goroutine里,如果通道改成容量为3的缓冲通道,则可以放到主goroutine里
	// closer
	go func() {
		wg.Wait()
		close(sizes)
	}()

    // 在对sizes进行for循环前要先关闭,也就是在源代码中去掉close(sizes)是不行,程序原地阻塞住了
	var total int64
	for size := range sizes {
		total += size
	}
	return total
}

注意,在closer goroutine中,在关闭sizes通道之前,等待所有的工作者结束。这里两个操作(等待和关闭)必须和在sizes通道上面的迭代并行执行。放在循环之前的主goroutine中,因为通道会满,它将永不结束,放在循环后面,没有东西关闭通道,它将不可达。

上面这段是书中原文的解释,但了看几遍后还是不解,于是翻看了英文原版中的解释,仔细对比后发现其中的猫腻,也渐渐理解了原文的解释是什么意思:如果把wait and close 放在main goroutine,那么就会导致每一个goroutine阻塞在
sizes <- info.Size(),因为sizes是阻塞的并且此时没有goroutine从sizes读取数据。但如果此时有goroutine从sizes中读取数据,就会让前面那个for循环中循环创建的goroutine得以执行,继而defer wg.Done() 得以的执行,那么在单独goroutine中close(sizes)就会执行,close(sizes)执行完就会让最后的for循环执行完毕,不得不说,这个例子实在太巧妙了,理解的关键点在于closer goroutine对于main goroutine来说是立即执行完的,导致在sizes 上的for循环得以执行,才不会让往sizes发送数据的goroutine发生泄露。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值