0
点赞
收藏
分享

微信扫一扫

068-Go 并发编程(二)


并发编程的难点在于异常处理。今天我们继续研究缩略图的并发编程,还记得之前留下的问题吗?我们的程序没有对程序返回的错误做特殊照顾。在服务器开发领域,这样的程序的显然不够健壮。

1. 让程序能够处理错误

之前我们的 makeThumbnails 函数没有返回值,现在我们给它添加一个 error 类型的返回值。下面这个程序在 demo04.go 中。

func makeThumbnails(filenames []string) error {
// errors 是一个无缓冲的 channel
errors := make(chan error)
for _, f := range filenames {
go func(f string) {
_, err := thumbnail.ImageFile(f)
errors <- err
}(f)
}

for range filenames {
if err := <-errors; err != nil {
return err
}
}
return nil
}

func main() {
now := time.Now()
err := makeThumbnails(os.Args[1:])
if err != nil {
fmt.Println(err)
}
fmt.Printf("elapse:%.3f ms\n", 1000*time.Since(now).Seconds())
}

运行一下:


068-Go 并发编程(二)_golang


图1 运行结果


这次我换成了 4 核心 3.1GHz 的 cpu 主机,所以速度看起来比上一次实验要快很多(上次是 2 核心 2.3 GHz)。

这一次,看起来我们的程序已经支持了错误处理,but … 这个程序还是有 bug.

2. goroutine leak

简单分析一下:

假设 makeThumbnails 函数在处理第一幅图像时出错了,于是在执行

for range filenames {
if err := <-errors; err != nil {
return

的时候,makeThumbnails 会直接返回错误。看起来似乎没什么问题?是的,程序出错了,就应该返回错误。不过我们要讨论的问题不是这里,而是 makeThumbnails 创建的那几个 goroutines:

for _, f := range filenames {
go func(f string) {
_, err := thumbnail.ImageFile(f)
errors <- err
}(f)
}

  • 假设某个协程在执行 thumbnail.ImageFile 时返回错误,将 err 推入 erros channel,然后该协程结束。
  • for range 部分从 errors 中读取到 err 错误,直接返回。
  • makeThumbnails 已经因为错误返回,其它所有创建的协程将阻塞在 errors <- err 上无法返回,因为 errors 没有缓冲,而且也没有任何协程读取

这有什么影响呢?在 Golang 里,这种情况叫 groutine 泄露(goroutine leak)。在服务器开发领域,这是一件非常重要而且严肃的事情,尽管初期它对你的程序功能没什么影响,但是随着运行时间越来越长,最终你的服务就会因为资源耗尽而 crash.

在我们这个例子中,解决这个问题的办法非常简单,我们使用一个带缓冲的 errors 就行了,你可以修改成下面这样:

func makeThumbnails(filenames []string) error {
// errors 是一个带缓冲的 channel,大小和要处理的文件个数一致
errors := make(chan error, len(filenames))
for _, f := range filenames {
go func(f string) {
_, err := thumbnail.ImageFile(f)
errors <- err
}(f)
}

for range filenames {
if err := <-errors; err != nil {
return err
}
}
return nil

这样即便 makeThumbnails 因为错误提前返回,其它协程也能正常结束。

3. 总结

  • 知道什么是 goroutine 泄露
  • 知道如何解决 goroutine 泄露


举报

相关推荐

0 条评论