Golang 并发编程实践

人是一种高并发的物种,细品。golang

初识

对 Go 语言的第一印象就是其原生地支持并发编程,并且使用的是协程,比线程更加轻量。编程

ybk93cwmg3y51.jpg

关于进程、线程和协程的区别

  • 进程是“程序执行的一个实例” ,担当分配系统资源的实体。进程建立必须分配一个完整的独立地址空间。进程切换只发生在内核态。
  • 线程:线程是进程的一个执行流,独立执行它本身的程序代码,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程能够有一个或多个线程。
  • 协程:协程不是进程或线程,其执行过程更相似于子例程,或者说不带返回值的函数调用。在语言级别能够建立并发协程,而后编写代码去进行管理。Go 将这一步承包下来,使协程并发运行成本更低。

Go 实现最简单的并发

for i := 0; i < 10; i++ {
    go func(n int) {
        fmt.Println(n)
    }(i)
}

项目实践

最近有个项目须要同时调用多个 job,并要等待这些 job 完成以后才能往下执行。并发

串行执行 job

最开始,咱们拥有一个执行 job 的方法,而且串行执行全部的 job:函数

func buildJob(name string) {
    ...
}

buildJob("A")
buildJob("B")
buildJob("C")

并行执行 job

由于全部 job 是能够并发执行的,这样就不用必须等待上一个 job 执行完成后,才能继续执行其余 job。咱们可使用 Go 语言的关键字 go 来快速启用一个 goroutine,下面咱们将并发地执行三个 job:高并发

go buildJob("A")
go buildJob("B")
go buildJob("C")

等待全部 job 完成

怎样才能知道每一个 job 是否已经完成,这里可使用 channel 进行通讯,并使用 select 检查执行结果:post

func buildJob(ch chan error, name string) {
    var err error
    
    ... // build job
    
    ch <- err // finnaly, send the result into channel
}

func build() error {
    jobCount := 3
    errCh := make(err chan error, jobCount)
    defer close(errCh) // 关闭 channel

    go buildJob(errCh, "A")
    go buildJob(errCh, "B")
    go buildJob(errCh, "C")

    for {
        select {
        case err := <-ch:
            if err != nil {
                return err
            }
        }
        
        jobCount--
        if jobCount <= 0 {
            break
        }
    }
    
    return nil
}

发现问题

当 job A 执行失败时,build 方法会 return err 退出,并执行 close(errCh)。但是此时另外两个 job B 和 C 可能还没执行完成,同时也会把结果发给 errCh,但因为这个时候 errCh 已经被关闭了,会致使程序退出 panic: send on closed channel优化

优化代码

在给 channel 发送数据的时候,可使用接收数据的第二个值判断 channel 是否关闭:ui

func buildJob(ch chan error, name string) {
    var err error
    
    ... // build job
    
    if _, ok := <-ch; !ok {
        return
    }
    ch <- err // finnaly, send the result into channel
}

func build() error {
    jobCount := 3
    errCh := make(err chan error, jobCount)
    defer close(errCh) // 关闭 channel

    go buildJob(errCh, "A")
    go buildJob(errCh, "B")
    go buildJob(errCh, "C")

    for {
        select {
        case err := <-ch:
            if err != nil {
                return err
            }
        }
        
        jobCount--
        if jobCount <= 0 {
            break
        }
    }
    
    return nil
}

总结

Go 并发编程看似只须要一个关键字 go 就能够跑起来一个 goroutine,但真正实践中,仍是有须要问题须要去处理的。spa

原文连接:https://k8scat.com/posts/code-with-golang-concurrency/线程

相关文章
相关标签/搜索