并发与并行

并发不是并行,并发关乎结构,并行关乎执行。——Rob Pike,Go语言之父

并行

并行方案就是在处理器核数充足的情况下启动多个单线程应用的实例,这样每个实例“运行”在一个核上,尽可能多地利用多核计算资源。理论上在这种方案下应用的整体处理能力是与实例数量(小于或等于处理器核数)成正比的。但这种方案是有约束的,对于那些不支持在同一环境下部署多实例或同一用户仅能部署一个实例的应用,用传统的部署方式使之并行运行是有难度的甚至是无法实现的。image.png

并发

并发就是重新做应用结构设计,即将应用分解成多个在基本执行单元(图中这样的执行单元为操作系统线程)中执行的、可能有一定关联关系的代码片段(图中的模块1~模块N)。我们看到与并行方案中应用自身结构无须调整有所不同,并发方案中应用自身结构做出了较大调整,应用内部拆分为多个可独立运行的模块。这样虽然应用仍然以单实例的方式运行,但其中的每个内部模块都运行于一个单独的操作系统线程中,多核资源得以充分利用。image.png

并发的退出模式

分离模式

分离模式是使用最为广泛的goroutine退出模式。对于分离的goroutine,创建它的goroutine不需要关心它的退出,这类goroutine在启动后即与其创建者彻底分离,其生命周期与其执行的主函数相关,函数返回即goroutine退出。这类goroutine有两个常见用途:一次性任务,常驻后台任务。

join 模式

goroutine的创建者需要等待新goroutine结束。

等待一个 goroutine 退出

  1. package main
  2. import "time"
  3. func work(args ...interface{}) {
  4. if len(args) == 0 {
  5. return
  6. }
  7. interval, ok := args[0].(int)
  8. if !ok {
  9. return
  10. }
  11. time.Sleep(time.Second * (time.Duration(interval)))
  12. }
  13. func spawn(f func(args ...interface{}), args ...interface{}) chan struct{} {
  14. c := make(chan struct{})
  15. go func() {
  16. f(args...)
  17. c <- struct{}{}
  18. }()
  19. return c
  20. }
  21. func main() {
  22. done := spawn(work, 3)
  23. println("spawn a worker goroutine")
  24. <-done
  25. println("work done")
  26. }

spawn 函数使用典型的 goroutine 创建模式创建了一个 goroutine,main goroutine 作为创建者通过 spawn 函数返回的 channel 与新 goroutine 建立联系。

这个 channel 的用途就是在两个 goroutine 之间建立退出事件的“信号”通信机制。main goroutine 在创建完新 goroutine 后便在该 channel 上阻塞等待,直到新goroutine 退出前向该 channel 发送了一个信号。

获取 goroutine 的退出状态

package main

import (
    "errors"
    "fmt"
    "time"
)

var OK = errors.New("ok")

func work(args ...interface{}) error {
    if len(args) == 0 {
        return errors.New("invalid args")
    }
    interval, ok := args[0].(int)
    if !ok {
        return errors.New("invalid interval arg")
    }
    time.Sleep(time.Second * (time.Duration(interval)))
    return OK
}

func spawn(f func(args ...interface{}) error, args ...interface{}) chan error {
    c := make(chan error)
    go func() {
        c <- f(args...)
    }()
    return c
}

func main() {
    done := spawn(work, 3)
    println("spawn work1")
    err := <-done
    fmt.Println("work1 done :", err)

    done = spawn(work, "3")
    println("spawn work2")
    err = <-done
    fmt.Println("work2 done : ", err)
}

编写时要注意 channel 和返回值的类型。