Go语言不可变数据共享:无锁并发编程实践
# 前言
对共享数据的并发访问往往需要用到锁,而这是一个常见的性能瓶颈。而不可变数据共享式一种不需要用锁来保护共享数据的方式,创建后的数据永远不改变,这样就不会有竞争问题了。
# 案例一:共享配置
- 创建配置结构体
// config.go
type Config struct {
LogLevel string
Timeout time.Duration
Features map[string]bool // 必须深拷贝,原始map的修改会影响已创建的配置
}
1
2
3
4
5
6
2
3
4
5
6
- 每次获取配置都是独立的
func NewConfig(logLevel string, timeout time.Duration, features map[string]bool) *Config {
copiedFeatures := make(map[string]bool, len(features))
for k, v := range features {
copiedFeatures[k] = v
}
return &Config{
LogLevel: logLevel,
Timeout: timeout,
Features: copiedFeatures,
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- 使用 atomic.Value 来存储并安全的更新当前配置
var currentConfig atomic.Pointer[Config] // Go 1.19+ 特性
// LoadInitialConfig 初始化配置(必须保证线程安全)
func LoadInitialConfig() {
cfg := NewConfig("info", 5*time.Second, map[string]bool{"beta": true})
currentConfig.Store(cfg) // 原子存储初始配置
}
// GetConfig 安全获取当前配置(零锁消耗)
func GetConfig() *Config {
return currentConfig.Load() // 原子加载指针
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- 在处理程序中使用
func handler(w http.ResponseWriter, r *http.Request) {
cfg := GetConfig()
if cfg.Features["beta"] {
// Enable beta path
}
// Use cfg.Timeout, cfg.LogLevel, etc.
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# 案例二:不可变路由表
- 创建路由结构体
type Route struct {
Path string
Backend string
}
type RoutingTable struct {
Routes []Route
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 每次获取都是全新版本
func NewRoutingTable(routes []Route) *RoutingTable {
copied := make([]Route, len(routes))
copy(copied, routes)
return &RoutingTable{Routes: copied}
}
1
2
3
4
5
2
3
4
5
- 以原子的方式存储及修改
var currentRoutes atomic.Pointer[RoutingTable]
func LoadInitialRoutes() {
table := NewRoutingTable([]Route{
{Path: "/api", Backend: "http://api.internal"},
{Path: "/admin", Backend: "http://admin.internal"},
})
currentRoutes.Store(table)
}
func GetRoutingTable() *RoutingTable {
return currentRoutes.Load()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- 并发路由请求
func routeRequest(path string) string {
table := GetRoutingTable()
for _, route := range table.Routes {
if strings.HasPrefix(path, route.Path) {
return route.Backend
}
}
return ""
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 总结
应用场景
- 读多写少:配置信息、路由表等低频变更数据
- 高性能要求:需要避免锁竞争的热点路径
注意事项
- 深拷贝成本:当数据结构复杂时(嵌套map/slice),需要考虑拷贝性能
- 内存消耗:每次更新都会创建新对象,需权衡内存与性能
- 原子性保证:更新操作必须完全替换配置对象,避免部分更新
上次更新: 2025/06/14, 16:24:58