Golang 基本同步原语sync.Onece
sync.Once
是 Go 语言中一个简单但强大的同步原语,用于确保某个操作在并发场景下只执行一次。它常用于延迟初始化、单例模式、全局配置加载等场景
基本用法
sync.Once
的核心方法是 Do(f func())
,传入的函数 f
只会执行一次,即使多个 goroutine 同时调用 Do
。
内部实现原理
sync.Once
的源码非常简洁,其核心是通过一个 uint32
类型的原子标志位(done
)和一个互斥锁(m
)实现的:
package sync
import (
"sync/atomic"
)
type Once struct {
done uint32
m Mutex
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
使用 done
标记是否已经初始化,使用锁 m Mutex
实现线程安全。
使用案例
使用单例模式,只读取一次配置文件,完成程序初始化
config.go
package config
import (
"fmt"
"os"
"sync"
"github.com/pelletier/go-toml"
)
// Config 代表配置结构
type Config struct {
App AppConfig `toml:"app"`
}
// AppConfig 表示 [app] 下的配置
type AppConfig struct {
Port int `toml:"port"`
AppName string `toml:"app_name"`
}
var (
instance *Config
once sync.Once
)
// LoadConfig 加载 TOML 配置文件
func LoadConfig(filePath string) (*Config, error) {
var err error
once.Do(func() {
fmt.Println("Loading TOML configuration...") // 只会执行一次
instance, err = loadConfigFromFile(filePath)
})
if err != nil {
return nil, err
}
return instance, nil
}
// loadConfigFromFile 从文件中解析 TOML 配置
func loadConfigFromFile(filePath string) (*Config, error) {
file, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer file.Close()
decoder := toml.NewDecoder(file)
config := &Config{}
if err := decoder.Decode(config); err != nil {
return nil, err
}
return config, nil
}
config.toml
[app]
app_name = "MyApp"
port = 8080
main.go
package main
import (
"awesomeProject/config"
"fmt"
"log"
)
func main() {
// 假设配置文件路径为 config.toml
filePath := "config/config.toml"
cfg, err := config.LoadConfig(filePath)
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
fmt.Printf("App Name: %s, Port: %d\n", cfg.App.AppName, cfg.App.Port)
}