uber 原子操作库:atomic

Go 标准库已经提供了 sync/atomic,为什么还会出现 Uber 的 atomic?

答案并不是 Uber Atomic 提供了另一套原子操作机制,而是它在 Go 标准库原子操作之上,提供了一层更偏工程实践的类型封装。sync/atomic 是 Go 官方提供的标准原子操作 API,功能基础、依赖最少;go.uber.org/atomic 则在此基础上扩展了更多数据类型,并提供了一些更便捷、更具语义化的方法。

从使用方式来看,Uber Atomic 与现代 Go 的 sync/atomic 已经非常接近。除了整数类型,它还提供 Bool、String、Float64、Duration、Time、Pointer[T] 等类型,并针对不同类型提供了相应的方法。

那么,它与 sync/atomic 到底有哪些区别?这些额外的封装在今天是否仍然有必要?下面通过 API 和具体使用场景进行对比。

设计思路

go.uber.org/atomic 的价值主要在于历史和设计演进。

它诞生的时候,标准库的 atomic API 主要是:

atomic.AddInt64(&counter, 1)
atomic.LoadInt64(&counter)
atomic.StoreInt64(&counter, 100)

Uber Atomic 则把它包装成:

counter.Inc()
counter.Load()
counter.Store(100)

并进一步提供:

atomic.String
atomic.Duration
atomic.Float64
atomic.Time
atomic.Error

所以它实际上代表了一种设计思路:把底层原子操作包装成类型化、面向对象、业务语义更明确的并发原语。而这个设计后来被 Go 标准库大量吸收。

Golang 1.19 开始,sync/atomic 同样提供 atomic.Int64、atomic.Uint64、atomic.Bool、atomic.Pointer[T] 等类型。
Go 1.23 又增加了 And / Or 等操作。
因此两者在新版本 Go 中已经越来越接近,标准库已经逐渐走向 Uber Atomic 曾经倡导的方向。

与 sync/atomic 的主要区别

目前仍有不少项目在使用 Uber Atomic,而且它确实提供了一些标准库没有的类型和便捷 API,因此仍然值得了解。

例如,标准库可以这样实现原子计数:

var counter atomic.Int64

counter.Add(1)
counter.Load()
counter.Store(10)

Uber Atomic 则提供了更具语义化的 Inc()、Dec() 等方法:

var counter atomic.Int64

counter.Inc()
counter.Load()
counter.Store(10)

对于简单的整数操作,两者已经非常接近。Uber Atomic 更明显的差异在于,它额外提供了 String、Duration、Float64、Time 等原子类型,以及 Inc()、Dec()、Bool.Toggle() 等便捷 API。

对比项 sync/atomic go.uber.org/atomic
维护方 Go 官方 Uber
依赖 标准库 第三方库
底层机制 标准库原子操作 基于 sync/atomic
Int64 支持 支持
Bool 支持 支持
CAS 支持 支持
Pointer[T] 支持 支持
Inc() / Dec() 使用 Add() 直接提供
String 无专门类型 支持
Duration 无专门类型 支持
Float64 无专门类型 支持
Time 无专门类型 支持

两者应该怎么选?

如果是普通 Go 项目,优先使用 sync/atomic。它属于标准库,没有第三方依赖,而且现代 Go 的类型化 API 已经解决了过去 atomic.AddInt64(&value, 1) 这类 API 可读性较差的问题。

Uber Atomic 更适合已经使用该库的项目,或者确实需要 String、Duration、Float64、Time 等标准库没有直接提供的原子类型,以及 Inc()、Dec() 等便捷 API 的场景。

对于今天的新 Go 项目而言,sync/atomic 已经足够覆盖绝大多数原子操作场景。引入 Uber Atomic 的主要理由应该是额外的类型和 API 便利性,而不是为了获得一种不同的原子操作机制或更高的性能。

如果你有魔法,你可以看到一个评论框~