You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go内存模型:如何正确同步地为字段赋值?

关于Go中Promise实现的内存同步疑问

以下是我在Go中实现Promise的核心代码:

// A promise represents the future result of a call to a function.
type promise struct {
    // state is the current state of this promise.
    state int32
    // done is closed when execution completes to unblock concurrent waiters.
    done chan struct{}
    // the function that will be used to populate the outcome.
    function Function
    // outcome is set when execution completes.
    outcome Outcome
}

// get returns the value associated with a promise.
//
// All calls to promise.get on a given promise return the same result
// but the function is called (to completion) at most once.
//
// - If the underlying function has not been invoked, it will be.
// - If ctx is cancelled, get returns (nil, context.Canceled).
func (p *promise) get(ctx context.Context) Outcome {
    if ctx.Err() != nil {
        return Outcome{
            Value: nil,
            Err:   ctx.Err(),
        }
    }

    if p.changeState(IsCreated, IsExecuted) {
        return p.run(ctx)
    }

    return p.wait(ctx)
}

// run starts p.function and returns the result.
func (p *promise) run(ctx context.Context) Outcome {
    go func() {
        v, err := doExecute(ctx, p.function)

        p.outcome = Outcome{
            Value: v,
            Err:   err,
        }
        p.function = nil // aid GC
        close(p.done)
    }()

    return p.wait(ctx)
}

// wait waits for the value to be computed, or ctx to be cancelled.
func (p *promise) wait(ctx context.Context) Outcome {
    select {
    case <-p.done:
        return p.outcome

    case <-ctx.Done():
        return Outcome{
            Value: nil,
            Err:   ctx.Err(),
        }
    }
}

func (p *promise) changeState(from, to State) bool {
    return atomic.CompareAndSwapInt32(&p.state, int32(from), int32(to))
}

我看到Go内存模型中的一个示例,代码如下:

var a, b int

func f() {
    a = 1
    b = 2
}

func g() {
    print(b)
    print(a)
}

func main() {
    go f()
    g()
}

该示例说明g函数可能先打印2再打印0,文档中提到:

Programs with races are incorrect and can exhibit non-sequentially consistent executions. In particular, note that a read r may observe the value written by any write w that executes concurrently with r. Even if this occurs, it does not imply that reads happening after r will observe writes that happened before w.

我此前一直认为,在关闭done通道前设置变量,能保证其他协程看到该变量的最新值。但上述例子让我产生疑问:使用done通道是否真的有区别?其他协程会不会检测到done通道已关闭,却读取到尚未更新的字段值?


解答

你的认知是正确的,使用done通道的关闭操作与后续的接收操作,完全可以保证内存可见性。

根据Go内存模型的规则:

  • 通道的关闭操作发生在因通道关闭而返回零值的接收操作完成之前。
  • 在你的代码中,协程内先完成p.outcome的赋值,再执行close(p.done),这两个操作在同一个协程内是顺序执行的,因此赋值操作发生在关闭操作之前。
  • 当其他协程通过<-p.done检测到通道关闭后,后续读取p.outcome的操作一定能看到之前赋值的最新值,不存在读取到旧值的情况。

而你提到的内存模型示例,问题在于代码中没有任何同步原语,两个协程的操作完全并发,没有明确的发生先后关系,因此才会出现非顺序一致的执行结果。这和你的Promise实现有本质区别——你的代码通过通道操作建立了明确的同步关系,不存在数据竞争的问题。

字段读写同步的正确方式

在Go中,保证并发场景下字段读写同步的常用方式包括:

  • 通道操作:如你的实现中使用的关闭/接收通道,发送/接收通道都能建立同步关系,保证内存可见性。
  • 原子操作:如你代码中使用的atomic.CompareAndSwapInt32,适用于简单的数值类型读写同步。
  • 互斥锁:使用sync.Mutex或sync.RWMutex,通过加锁解锁操作建立同步,适合复杂的临界区场景。

内容的提问来源于stack exchange,提问作者Mr.J4mes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 18:15:04