这个Go代码线程是安全的还是需要互斥锁?

时间:2018-03-18 15:16:07

标签: multithreading go mutex race-condition

假设我有以下函数doWork,它在goroutine中启动一些工作并返回Result以检查完成和错误:

func doWork() *Result {
    r := Result{doneCh: make(chan struct{})}
    go func() {
        var err error
        defer func() {
            r.err = err
            close(r.doneCh)
        }()
        // do some work
    }()
    return &r
}

其中Result是以下结构:

type Result struct {
    doneCh      chan struct{}
    err         error
}
// doneCh returns a closed chan when the work is done.
func (r *Result) Done() <-chan struct{} {
    return r.doneCh
}
// Err returns a non-nil err if the work failed.
// Don't call Err until Done returns a closed chan.
func (r *Result) Err() error {
    return r.err
}
如果我在关闭err之前设置doneCh

,那么

是否可以安全使用此代码线程

defer func() {
    r.err = err
    close(r.doneCh)
}()

或编译器是否可以随意订购r.err = errclose(r.doneCh)指令,在这种情况下,我需要一个互斥锁来防止错误时发生并发读/写。

3 个答案:

答案 0 :(得分:3)

只有遵守您的意见并且在Err()的读取返回之前永远不会调用Done(),它才是线程安全的。

您可以通过重新实现它来简单地阻止Err()

func (r *Result) Err() error {
    <-r.doneCh
    return r.err
}

这将保证Err()仅在完成后才返回。鉴于错误将在工作错误之前为零,您无法告知Err()是否因为工作已完成或因为尚未完成或错误而成功返回,除非您先阻止Done() ,在这种情况下,为什么不只是让Err()阻止?

答案 1 :(得分:1)

编译器可能不会重新排序赋值和关闭语句,因此如果调用者表现良好并且按照文档的指示执行操作,则不需要互斥锁。

The Go Memory Model, Channel Communication中解释了这一点。

答案 2 :(得分:0)

您是否尝试过使用chan error并测试频道是否在接收时打开或关闭?

package main

import (
    "errors"
    "fmt"
)

func delegate(work func(ch chan error)) {
    ch := make(chan error)

    go work(ch)

    for {
        err, opened := <- ch
        if !opened {
            break
        }
        // Handle errors
        fmt.Println(err)
    }
}

func main() {
    // Example: error
    delegate(func(ch chan error) {
        defer close(ch)
        // Do some work
        fmt.Println("Something went wrong.")
        ch <- errors.New("Eyyyyy")
    })

    // Example: success
    delegate(func(ch chan error) {
        defer close(ch)
        // Do some work
        fmt.Println("Everything went fine.")
    })

    // Example: error
    delegate(func(ch chan error) {
        defer close(ch)
        // Do some work
        fmt.Println("Something went wrong more than once.")
        ch <- errors.New("Eyyyyy 1")
        ch <- errors.New("Eyyyyy 2")
        ch <- errors.New("Eyyyyy 3")
    })
}