dongweicha6077
2018-03-18 15:16
浏览 53
已采纳

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

Suppose I have the following function, doWork, that starts some work in a goroutine and returns a Result to check for completion and error:

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
}

where Result is the following struct:

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
}

is this code thread safe if I set err before closing doneCh:

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

or is the compiler free to order the r.err = err and close(r.doneCh) instructions as it likes, in which case I'd need a mutex to prevent concurrent read/writes on error.

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

3条回答 默认 最新

  • dqcuq4138 2018-03-18 15:56
    已采纳

    The compiler may not reorder the assignment and close statement, so you do not need a mutex if callers are well-behaved and do as instructed by your docs.

    This is explained in The Go Memory Model, Channel Communication.

    点赞 打赏 评论
  • duanniwu7730 2018-03-18 16:05

    It is thread-safe only if your comments are obeyed and Err() is never called until a read from Done() returns.

    You could simply make Err() blocking though by re-implementing it as:

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

    Which would guarantee that Err() only returns after done is complete. Given that err will be nil until the work errors, you have no way of telling if Err() is returning successfully because work was finished or because it hasn't completed or errored yet unless you block on Done() first, in which case why not just make Err() blocking?

    点赞 打赏 评论
  • duanen19871021 2018-03-19 14:56

    Have you tried using chan error and testing if the channel is opened or closed on reception?

    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")
        })
    }
    
    点赞 打赏 评论

相关推荐 更多相似问题