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

Is this Go code thread safe or do I need a mutex?

假设我有以下函数,doWork,它在 goroutine 中开始一些工作,returns 一个 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) 指令进行排序,在这种情况下,我需要一个互斥锁来防止并发 read/writes 出错。

编译器可能不会重新排序赋值和关闭语句,因此如果调用者是 well-behaved 并且按照您的文档的指示进行操作,则不需要互斥体。

这在 The Go Memory Model, Channel Communication 中有解释。

它是 thread-safe 仅当您的评论被遵守并且 Err() 在从 Done() returns.

读取之前永远不会被调用

您可以简单地通过 re-implementing 将 Err() 阻塞为:

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

这将保证 Err() 只有 returns 完成后。鉴于 err 在工作出错之前将为零,您无法判断 Err() 是否成功返回,因为工作已完成,或者因为它尚未完成或出错,除非您首先阻止 Done() ,在这种情况下,为什么不让 Err() 阻塞?

您是否尝试过使用 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")
    })
}