0

0

Golang的sync/atomic包提供了哪些原子操作以避免锁竞争

P粉602998670

P粉602998670

发布时间:2025-09-13 08:09:01

|

436人浏览过

|

来源于php中文网

原创

sync/atomic通过CPU指令提供整数和指针类型的原子操作,如Add、CompareAndSwap、Load、Store、Swap及Value类型,实现无锁并发安全,适用于计数器、标志位、配置更新等简单场景,性能优于sync.Mutex;而sync.Mutex适用于保护复杂数据结构或临界区含耗时操作的场景,两者选择需权衡操作复杂度与性能需求。

golang的sync/atomic包提供了哪些原子操作以避免锁竞争

Golang的

sync/atomic
包提供了一系列底层的、由CPU指令支持的原子操作,主要针对基本数据类型,如整数和指针。这些操作允许在不使用传统互斥锁(
sync.Mutex
)的情况下,对共享变量进行并发安全的读写、增减或比较并交换,从而有效减少锁竞争,提升高并发场景下的性能。

解决方案

sync/atomic
包提供了以下主要的原子操作,它们都以
atomic.
为前缀:

  • AddInt32
    /
    AddInt64
    /
    AddUint32
    /
    AddUint64
    : 原子性地将一个值加到现有变量上,并返回新值。
    var counter int64
    atomic.AddInt64(&counter, 1) // counter现在是1
  • CompareAndSwapInt32
    /
    CompareAndSwapInt64
    /
    CompareAndSwapUint32
    /
    CompareAndSwapUint64
    /
    CompareAndSwapPointer
    : 比较并交换(CAS)操作。如果变量的当前值等于期望值,则原子性地将其更新为新值,并返回
    true
    ;否则不进行任何操作并返回
    false
    。这是许多无锁算法的基石。
    var value int32 = 10
    // 如果value当前是10,就把它设置为20
    swapped := atomic.CompareAndSwapInt32(&value, 10, 20) // swapped为true, value为20
    // 如果value当前是10(现在是20了),就把它设置为30
    swapped = atomic.CompareAndSwapInt32(&value, 10, 30) // swapped为false, value仍为20
  • LoadInt32
    /
    LoadInt64
    /
    LoadUint32
    /
    LoadUint64
    /
    LoadPointer
    /
    LoadValue
    : 原子性地读取变量的当前值。这确保了即使在并发写入时,也能获取到最新的、完整的变量状态,避免了部分写入的问题。
    var config atomic.Value
    config.Store("initial_config")
    loadedConfig := config.Load().(string) // loadedConfig为"initial_config"
  • StoreInt32
    /
    StoreInt64
    /
    StoreUint32
    /
    StoreUint64
    /
    StorePointer
    /
    StoreValue
    : 原子性地将一个新值写入变量。与
    Load
    类似,它确保写入操作的完整性。
    var status int32
    atomic.StoreInt32(&status, 1) // status现在是1
  • SwapInt32
    /
    SwapInt64
    /
    SwapUint32
    /
    SwapUint64
    /
    SwapPointer
    : 原子性地将变量设置为新值,并返回变量的旧值。
    var oldVal int64 = 5
    // 将oldVal设置为10,并返回它原来的值5
    previous := atomic.SwapInt64(&oldVal, 10) // previous为5, oldVal为10
  • Value
    类型
    :
    atomic.Value
    是一个特殊的原子操作,它可以存储任意类型的接口值,并提供
    Load()
    Store()
    方法进行原子性的读写。它常用于存储配置或不可变的数据结构,避免在更新时锁定整个结构。

为什么选择
sync/atomic
而非
sync.Mutex
来处理并发?

这问题问得好,因为很多时候我们写并发代码,第一反应就是上锁。但实际上,

sync/atomic
sync.Mutex
解决的是不同粒度、不同场景下的并发问题。

sync.Mutex
是一种悲观锁,它假设多个协程会同时访问共享资源并产生冲突,因此在访问前就将资源锁住,确保同一时间只有一个协程能操作。这就像你进图书馆借书,为了避免别人拿走你要的书,你直接把整个书架都锁起来,等你自己拿完再解锁。这种方式简单、安全,适用于保护复杂的共享数据结构,或者涉及多个操作步骤需要原子性完成的场景。

立即学习go语言免费学习笔记(深入)”;

sync/atomic
则是一种乐观锁的思路。它不锁住资源,而是尝试直接对共享变量进行操作。如果操作成功(即在操作期间没有其他协程修改该变量),那就完成了;如果失败(说明有其他协程抢先修改了),它会重试。这就像你借书,你直接去拿,如果书还在就拿走,如果不在了就再去看看有没有别的书。这种方式通常由CPU的特定指令(如CAS指令)支持,避免了操作系统层面的上下文切换,因此对于简单的数值或指针操作,它的性能开销远低于互斥锁。

我个人经验来看,当你只是想对一个计数器进行增减,或者更新一个配置指针,却用了

sync.Mutex
,那往往是“杀鸡用牛刀”了。互斥锁引入的开销,尤其是在高并发竞争激烈时,会非常显著。
sync/atomic
在这些场景下,不仅能提供更好的性能,代码也可能更简洁。但反过来,如果你的操作涉及多个字段的更新,或者需要维护复杂的数据结构一致性,那
sync.Mutex
的安全性就显得不可替代了。选择哪个,真的要看具体需求和场景。

CompareAndSwap
(CAS)操作在
sync/atomic
中扮演什么核心角色?

CompareAndSwap
(CAS),或者说比较并交换,绝对是
sync/atomic
包乃至整个现代并发编程中的一个核心概念。它不是简单地设置一个值,而是先检查一个条件(当前值是否等于期望值),如果条件满足,才执行更新操作。这个“检查-更新”的整个过程是原子性的,不可中断。

你可以把它想象成一个守门员,你告诉他:“如果现在是A状态,就把门打开到B状态。”守门员会迅速检查,如果确实是A,他会立即打开到B,并告诉你“搞定!”。如果他发现已经不是A了(比如别人在他检查前已经改成了C),他会告诉你“不行,状态不对!”。

X Detector
X Detector

最值得信赖的多语言 AI 内容检测器

下载

CAS的强大之处在于,它允许我们构建无锁(lock-free)或免锁(wait-free)的数据结构和算法。传统的锁机制,比如

sync.Mutex
,在竞争激烈时,会频繁地导致协程阻塞和唤醒,带来上下文切换的开销。而基于CAS的算法,协程在发现冲突时,不会被阻塞,而是选择重试,这在某些场景下能提供更高的吞吐量和更低的延迟。

举个例子,一个简单的原子计数器就可以用CAS来实现,尽管

atomic.AddInt64
更直接。但更复杂的,比如实现一个无锁的栈(stack)或队列(queue),CAS就是不可或缺的。你需要用CAS来更新栈顶指针或队列的头尾指针,确保在多协程同时出入栈/队时,数据结构依然保持一致性。

当然,CAS也不是万能药。它可能会导致“ABA”问题(如果一个值从A变为B,又变回A,CAS会误以为没有发生变化),虽然在Go的标准库中,对于基本类型这通常不是大问题,但在构建复杂无锁结构时需要考虑。此外,频繁的CAS失败重试也可能导致“忙等”(busy-waiting),消耗CPU资源。但无论如何,理解CAS是深入理解现代并发编程的关键一步。

在实际项目中,何时优先考虑使用
sync/atomic
而不是
sync.Mutex

在实际开发中,选择

sync/atomic
还是
sync.Mutex
,这其实是一个权衡和取舍的问题,没有绝对的答案,但有一些清晰的指导原则。

我通常会优先考虑

sync/atomic
的场景:

  1. 简单的计数器或标志位:这是最典型的应用场景。比如统计请求数量、并发任务数、或者一个布尔型的开关状态。使用
    atomic.AddInt64
    atomic.StoreInt32
    来更新,比加锁解锁效率高得多。
    var requestCount int64
    // 每次处理请求
    atomic.AddInt64(&requestCount, 1)
  2. 单个指针的原子性更新:当需要原子性地更换一个指向不可变数据结构的指针时,
    atomic.Pointer
    atomic.Value
    非常有用。例如,更新一个全局配置对象,你可以创建一个新的配置对象,然后原子性地替换旧的指针,这样读取方总能看到一个完整的、一致的配置版本,而不需要加锁。
    type Config struct {
        // ... 配置字段
    }
    var currentConfig atomic.Value // 存储 *Config
    // 初始化
    currentConfig.Store(&Config{/* ... */})
    // 更新配置
    newConfig := &Config{/* ... */}
    currentConfig.Store(newConfig)
    // 读取配置
    cfg := currentConfig.Load().(*Config)
  3. 性能敏感的临界区:如果你的代码段非常短,只涉及对一个基本类型变量的读写,并且这个代码段是系统性能瓶颈之一,那么
    sync/atomic
    可以显著降低开销。比如,在一个高并发的缓存系统中,更新缓存命中率的统计。
  4. 构建无锁数据结构的基础:如果你正在尝试实现一些高级的无锁数据结构(如无锁队列、无锁哈希表),那么CAS操作(
    CompareAndSwap
    )是构建这些结构的基础。但这通常是高级且复杂的任务,需要对并发模型有深刻理解。

反之,我会倾向于使用

sync.Mutex
的场景:

  1. 复杂数据结构的操作:当你需要保护
    map
    slice
    、包含多个字段的结构体,或者需要执行一系列操作(比如先检查、再修改、再记录日志)作为一个整体原子性完成时,
    sync.Mutex
    是更安全、更易于理解和维护的选择。
    atomic
    操作只能针对单个变量。
  2. 临界区包含I/O或耗时操作:如果你的临界区中包含了文件I/O、网络请求或者其他可能长时间阻塞的操作,那么使用
    sync.Mutex
    是合适的。因为这些操作的耗时远超锁本身的开销,而且
    atomic
    的忙等重试在这种情况下会浪费大量CPU周期。
  3. 代码可读性和维护性优先:对于大多数业务场景,
    sync.Mutex
    的代码逻辑更直观,出错的概率也相对较低。
    atomic
    虽然性能好,但它的语义更底层,如果使用不当,很容易引入难以调试的并发bug。

总结来说,

sync/atomic
是针对简单、单值操作的“快车道”,适用于对性能有极致要求的特定场景。而
sync.Mutex
则是“安全通道”,适用于大多数需要保证数据一致性的复杂场景。在做选择时,我的建议是:先考虑
sync.Mutex
,如果分析后发现它确实是性能瓶颈,并且操作足够简单,才转向
sync/atomic
过早优化往往是万恶之源。

相关专题

更多
golang如何定义变量
golang如何定义变量

golang定义变量的方法:1、声明变量并赋予初始值“var age int =值”;2、声明变量但不赋初始值“var age int”;3、使用短变量声明“age :=值”等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

178

2024.02.23

golang有哪些数据转换方法
golang有哪些数据转换方法

golang数据转换方法:1、类型转换操作符;2、类型断言;3、字符串和数字之间的转换;4、JSON序列化和反序列化;5、使用标准库进行数据转换;6、使用第三方库进行数据转换;7、自定义数据转换函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

226

2024.02.23

golang常用库有哪些
golang常用库有哪些

golang常用库有:1、标准库;2、字符串处理库;3、网络库;4、加密库;5、压缩库;6、xml和json解析库;7、日期和时间库;8、数据库操作库;9、文件操作库;10、图像处理库。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

337

2024.02.23

golang和python的区别是什么
golang和python的区别是什么

golang和python的区别是:1、golang是一种编译型语言,而python是一种解释型语言;2、golang天生支持并发编程,而python对并发与并行的支持相对较弱等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

208

2024.03.05

golang是免费的吗
golang是免费的吗

golang是免费的。golang是google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的开源编程语言,采用bsd开源协议。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

389

2024.05.21

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

195

2025.06.09

golang相关判断方法
golang相关判断方法

本专题整合了golang相关判断方法,想了解更详细的相关内容,请阅读下面的文章。

191

2025.06.10

golang数组使用方法
golang数组使用方法

本专题整合了golang数组用法,想了解更多的相关内容,请阅读专题下面的文章。

192

2025.06.17

Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

36

2026.01.14

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 7.1万人学习

Django 教程
Django 教程

共28课时 | 3.1万人学习

Pandas 教程
Pandas 教程

共15课时 | 0.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号