在Golang的并发编程场景中,多个goroutine同时读写同一个共享变量时,很容易出现数据竞争的问题,导致程序运行结果不符合预期。传统的解决方式是使用互斥锁或者读写锁来保证同一时间只有一个goroutine能操作共享变量,但锁的加解锁过程会带来额外的性能开销。而sync/atomic包提供了一系列底层的原子操作函数,能够直接对基础类型的变量进行原子性的修改、读取、交换等操作,不需要加锁就能保证并发安全,在很多轻量级的并发场景下能显著提升程序性能。

sync/atomic支持的基础类型
sync/atomic包主要针对以下几类基础类型提供原子操作支持:
- int32、int64、uint32、uint64、uintptr等整数类型
- unsafe.Pointer指针类型
- Go 1.19之后新增的atomic.Bool、atomic.Int32、atomic.Int64等泛型原子类型
常用原子操作函数及使用示例
1. 原子读取和写入
普通的变量读取和写入在并发场景下可能不是原子的,使用atomic.Load和atomic.Store系列函数可以保证操作的原子性。
package main
import (
"fmt"
"sync"
"sync/atomic"
)
var count int64
func main() {
var wg sync.WaitGroup
// 启动10个goroutine并发修改count
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// 原子增加count的值
atomic.AddInt64(&count, 1)
}()
}
wg.Wait()
// 原子读取count的值
fmt.Println("最终count值:", atomic.LoadInt64(&count))
}
2. 原子增减操作
atomic.Add系列函数可以对整数类型进行原子性的加减操作,返回修改后的值。
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var num int32 = 10
// 原子加5
newVal := atomic.AddInt32(&num, 5)
fmt.Println("加5后的值:", newVal) // 输出15
// 原子减3,传入负数即可
newVal = atomic.AddInt32(&num, -3)
fmt.Println("减3后的值:", newVal) // 输出12
}
3. 原子交换和比较并交换
atomic.Swap系列函数可以直接交换变量的值,返回旧值;atomic.CompareAndSwap(简称CAS)系列函数会比较变量的当前值是否等于预期值,如果相等则替换为新值,返回是否替换成功。
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var val int64 = 20
// 原子交换,将val设为100,返回旧值20
old := atomic.SwapInt64(&val, 100)
fmt.Println("交换前的旧值:", old) // 输出20
fmt.Println("交换后的新值:", val) // 输出100
// CAS操作,预期val是100,替换为200
swapped := atomic.CompareAndSwapInt64(&val, 100, 200)
fmt.Println("CAS是否成功:", swapped) // 输出true
fmt.Println("CAS后的值:", val) // 输出200
// 再次CAS,预期val是100,实际是200,替换失败
swapped = atomic.CompareAndSwapInt64(&val, 100, 300)
fmt.Println("第二次CAS是否成功:", swapped) // 输出false
fmt.Println("第二次CAS后的值:", val) // 输出200
}
Go 1.19之后的泛型原子类型
Go 1.19引入了泛型原子类型,使用起来比之前的全局函数更加简洁,不需要手动传递指针,也不容易写错类型。
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
// 使用atomic.Int64类型
var counter atomic.Int64
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Add(1) // 原子加1
}()
}
wg.Wait()
fmt.Println("counter最终值:", counter.Load()) // 输出5
// 使用atomic.Bool类型
var flag atomic.Bool
flag.Store(true) // 原子设置为true
fmt.Println("flag当前值:", flag.Load()) // 输出true
}
原子操作与互斥锁的对比
原子操作和互斥锁都能保证并发安全,但适用场景和性能表现有所不同,具体对比如下:
| 对比维度 | 原子操作 | 互斥锁 |
|---|---|---|
| 适用场景 | 简单的基础类型变量的读写、修改操作 | 复杂的逻辑操作、多变量同时修改的场景 |
| 性能开销 | 底层硬件指令实现,开销极小 | 需要加解锁、可能的协程调度,开销较大 |
| 功能复杂度 | 仅支持简单的原子操作,不支持复杂逻辑 | 可以保护任意复杂的代码块 |
| 死锁风险 | 无 | 如果加解锁逻辑错误可能出现死锁 |
使用原子操作的注意事项
- 原子操作仅适用于sync/atomic支持的基础类型,不能直接用于结构体、切片、映射等复杂类型。
- 不要对同一个变量混合使用原子操作和非原子操作,否则仍然会出现数据竞争问题。
- CAS操作在高竞争场景下可能会出现大量失败重试,此时性能可能不如互斥锁,需要根据实际场景测试选择。
- 使用unsafe.Pointer的原子操作时,需要保证指针指向的内存生命周期正确,避免出现悬垂指针。
原子操作是Golang并发编程中轻量级并发控制的重要手段,合理使用能够在不牺牲并发安全的前提下显著提升程序性能,开发者需要根据操作复杂度和并发场景选择合适的并发控制方案。
Golangsync/atomic原子操作并发安全性能优化修改时间:2026-07-02 09:03:40