您好,欢迎来到12图资源库!分享精神,快乐你我!我们只是素材的搬运工!!
  • 首 页
  • 当前位置:首页 > 开发 > WEB开发 >
    从 bug 中学习:六大开源项目通知你 go 并发编程的那些坑
    时间:2021-02-24 21:21 来源:网络整理 作者:网络 浏览:收藏 挑错 推荐 打印

    并发编程中,go不只仅支持传统的经过共享内存的方式来通讯,更推崇经过channel来传递音讯,这种新的并发编程模型会出现不同于以往的bug。从bug中学习,《Understanding Real-World Concurrency Bugs in Go》这篇paper在剖析了六大开源项目并发相关的bug之后,为我们总结了go并发编程中常见的坑。别往坑里跳,编程更美妙。

    从 bug 中学习:六大开源项目通知你 go 并发编程的那些坑

    在 go 中,创立 goroutine 十分复杂,在函数调用前加 go 关键字,这个函数的调用就在一个独自的 goroutine 中执行了;go 支持匿名函数,让创立 goroutine 的操作愈加繁复。另外,在并发编程模型上,go 不只仅支持传统的经过共享内存的方式来通讯,更推崇经过 channel 来传递音讯:

    Do not communicate by sharing memory; instead, share memory by communicating.

    这种新的并发编程模型会带来新类型的 bug,从 bug 中学习,《Understanding Real-World Concurrency Bugs in Go》这篇 paper 在 Docker、Kubernetes、etcd、gRPC、CockroachDB、BoltDB 六大开源项目的 commit log 中搜索"race"、"deadlock"、"synchronization"、"concurrency"、"lock"、"mutex"、"atomic"、"compete"、"context"、"once"、"goroutine leak"等关键字,找出了这六大项目中并发相关的 bug,然后归类这些 bug,总结出了 go 并发编程中常见的一些坑。经过学习这些坑,可以让我们在以后的项目里防范相似的错误,或许遇到相似成绩的时分可以协助指点快速定位排查。

    unbuffered channel 由于 receiver 参加招致 sender 侧 block

    如下面一个 bug 的例子:

    func finishReq(timeout time.Duration) ob { 

        ch := make(chan ob) 

        go func() { 

            result := fn() 

            ch <- result // block 

        }() 

        select { 

        case result = <-ch: 

            return result 

        case <-time.After(timeout): 

            return nil 

        } 

    本意是想调用 fn()时加上超时的功用,假设 fn()在超时时间没有前往,则前往 nil。但是当超时发作的时分,针对代码中第二行创立的 ch 来说,由于曾经没有 receiver 了,第 5 行将会被 block 住,招致这个 goroutine 永远不会参加。

    If the capacity is zero or absent, the channel is unbuffered and communication succeeds only when both a sender and receiver are ready. Otherwise, the channel is buffered and communication succeeds without blocking if the buffer is not full (sends) or not empty (receives).

    这个 bug 的修复方式也是十分的复杂,把 unbuffered channel 修正成 buffered channel。

    func finishReq(timeout time.Duration) ob { 

        ch := make(chan ob, 1

        go func() { 

            result := fn() 

            ch <- result // block 

        }() 

        select { 

        case result = <-ch: 

            return result 

        case <-time.After(timeout): 

            return nil 

        } 

    思索:在下面的例子中,虽然这样不会 block 了,但是 channel 不断没有被封锁,channel 保持不封锁能否会招致资源的走漏呢?

    WaitGroup 误用招致阻塞

    下面是一个 WaitGroup 误用招致阻塞的一个 bug 的例子: https:// github.com/moby/moby/pu ll/25384

    var group sync.WaitGroup 

    group.Add(len(pm.plugins)) 

    for _, p := range pm.plugins { 

        go func(p *plugin) { 

            defer group.Done() 

        }(p) 

        group.Wait() 

    当 len(pm.plugins)大于等于 2 时,第 7 行将会被卡住,由于这个时分只启动了一个异步的 goroutine,group.Done()只会被调用一次,group.Wait()将会永世阻塞。修复如下:

    var group sync.WaitGroup 

    group.Add(len(pm.plugins)) 

    for _, p := range pm.plugins { 

        go func(p *plugin) { 

            defer group.Done() 

        }(p) 

    group.Wait() 

    context 误用招致资源走漏

    如下面的代码所示:

    hctx, hcancel := context.WithCancel(ctx) 

    if timeout > 0 { 

        hctx, hcancel = context.WithTimeout(ctx, timeout) 

    第一行 context.WithCancel(ctx)有能够会创立一个 goroutine,来等候 ctx 能否 Done,假设 parent 的 ctx.Done()的话,cancel 掉 child 的 context。也就是说 hcancel 绑定了一定的资源,不能直接掩盖。

    Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete.

    这个 bug 的修复方式是:

    var hctx context.Context 

    var hcancel context.CancelFunc 

    if timeout > 0 { 

        hctx, hcancel = context.WithTimeout(ctx, timeout) 

    else { 

        hctx, hcancel = context.WithCancel(ctx) 

    或许

    hctx, hcancel := context.WithCancel(ctx) 

    if timeout > 0 { 

        hcancel.Cancel() 

        hctx, hcancel = context.WithTimeout(ctx, timeout) 

    多个 goroutine 同时读写共享变量招致的 bug

    如下面的例子:

    (责任编辑:admin)