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

Go语言单写入者单次写+多读取场景的同步方法选型

单写(仅一次)多读场景下的Go同步优化方案

问题背景

本场景的特殊性在于:写入者唯一且写入操作严格仅执行一次,同时存在大量并发读取者(比如并发调用的业务方法)。当前用sync.RWMutex实现同步,虽然比普通sync.Mutex性能更优,但全程加锁保护仅一次的写入,存在不必要的资源浪费,有没有更高效的实现方式?

最优方案:使用sync.Once

Go标准库中的sync.Once正是为"某个操作仅执行一次"的场景设计的同步原语,它通过原子操作保证写入(初始化)仅执行一次,后续读取无需加锁,能大幅降低高并发场景下的开销。

优化后的代码实现

package main

import (
    "sync"
)

type OnceErrorNotifier struct {
    once        sync.Once
    emailSender func(string)
}

func (a *OnceErrorNotifier) SetEmailSenderService(emailSender func(string)) {
    a.once.Do(func() {
        a.emailSender = emailSender
    })
}

func (a *OnceErrorNotifier) SendErrorMessage(errorMessage string) {
    // sync.Once保证写入完成后变量对所有goroutine可见,无需加锁读取
    if a.emailSender == nil {
        return
    }
    a.emailSender(errorMessage)
}

func main() {
    sender := &OnceErrorNotifier{}

    errorsCount := 100_000

    emailSender := func(emailMessage string) {
        // sending email...
    }

    var wg sync.WaitGroup // 仅用于演示并发场景

    wg.Add(errorsCount)

    for i := 0; i < errorsCount; i++ {
        go func() {
            sender.SendErrorMessage("ALARM!")
            wg.Done()
        }()
    }

    sender.SetEmailSenderService(emailSender) // 仅执行一次的写入操作

    wg.Wait()
}

方案对比说明

  1. 原RWMutex方案:每次读取都需要获取读锁,即使写入已经完成,仍会有锁竞争带来的性能损耗。
  2. sync.Once方案:
    • 写入操作通过Do方法保证仅执行一次,内部通过原子操作+懒加载锁实现,仅第一次调用Do时会进入锁定逻辑。
    • 写入完成后,所有读取操作直接访问变量,无需任何锁开销,在高并发读取场景下性能提升显著。

注意事项

  • sync.Once的Do方法仅保证传入的函数执行一次,即使多次调用SetEmailSenderService,后续调用也不会修改emailSender的值——这完全匹配本场景"严格仅一次写入"的要求;如果需要支持多次写入,仍需使用RWMutex。
  • 变量可见性:sync.Once已经内置内存屏障,保证写入完成后,变量对所有goroutine可见,无需额外处理。

内容的提问来源于stack exchange,提问作者IvanD

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 14:42:44