Go 语言核心 36 讲
郝林
《Go 并发编程实战》作者,前轻松筹大数据负责人
79610 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 55 讲
Go 语言核心 36 讲
15
15
1.0x
00:00/00:00
登录|注册

30 | 原子操作(下)

尽量不存储引用类型的值
不传递原子值及其指针值
不暴露原子变量给外界
存储的第一个值决定今后能存储的类型
不能存储nil
选择原子值或互斥锁的决策条件
使用建议
原子操作函数 vs. 原子值类型
使用建议
使用规则
原子地存储和加载任意值
保证对共享资源的完全保护
与for语句联用实现自旋锁
用途广泛
有条件的交换操作
交换(swap)
存储(store)
加载(load)
比较并交换(CAS)
加法(add)
总结
sync/atomic.Value
读操作的必要性
CAS操作
原子操作函数
原子操作

该思维导图由 AI 生成,仅供参考

你好,我是郝林,今天我们继续分享原子操作的内容。
我们接着上一篇文章的内容继续聊,上一篇我们提到了,sync/atomic包中的函数可以做的原子操作有:加法(add)、比较并交换(compare and swap,简称 CAS)、加载(load)、存储(store)和交换(swap)。并且以此衍生出了两个问题。
今天我们继续来看第三个衍生问题: 比较并交换操作与交换操作相比有什么不同?优势在哪里?
回答是:比较并交换操作即 CAS 操作,是有条件的交换操作,只有在条件满足的情况下才会进行值的交换。
所谓的交换指的是,把新值赋给变量,并返回变量的旧值。
在进行 CAS 操作的时候,函数会先判断被操作变量的当前值,是否与我们预期的旧值相等。如果相等,它就把新值赋给该变量,并返回true以表明交换操作已进行;否则就忽略交换操作,并返回false
可以看到,CAS 操作并不是单一的操作,而是一种操作组合。这与其他的原子操作都不同。正因为如此,它的用途要更广泛一些。例如,我们将它与for语句联用就可以实现一种简易的自旋锁(spinlock)。
for {
if atomic.CompareAndSwapInt32(&num2, 10, 0) {
fmt.Println("The second number has gone to zero.")
break
}
time.Sleep(time.Millisecond * 500)
}
for语句中的 CAS 操作可以不停地检查某个需要满足的条件,一旦条件满足就退出for循环。这就相当于,只要条件未被满足,当前的流程就会被一直“阻塞”在这里。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Go语言中的原子操作函数和`sync/atomic`包中的`atomic.Value`类型是并发编程中的重要概念。原子操作函数包括加法、比较并交换、加载、存储和交换等操作,而`atomic.Value`类型可以原子地存储和加载任意的值。在使用原子值时需要注意不应该再被复制,存储的第一个值决定了今后能且只能存储哪一个类型的值。文章提出了一些使用建议,如不要暴露原子变量给外界,不要传递原子值及其指针值,尽量不要在原子值中存储引用类型的值等。相对于原子操作函数,原子值类型的优势很明显,但它的使用规则也更多一些。总的来说,原子操作函数在某些场景下比互斥锁更适用,因为其执行速度更快,使用更简单,不涉及临界区选择和死锁等问题。但在选择原子值与互斥锁时需要仔细考量,需要牢记文章中提出的注意事项和使用建议。文章内容涉及了原子操作函数的用法、原理、对比和最佳实践,为读者提供了对并发编程中原子操作的全面了解。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 语言核心 36 讲》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(18)

  • 最新
  • 精选
  • Geek_f1933b
    郝老师,什么时候使用atomic.value呢,有没有具体的应用中的简单例子呢

    作者回复: 其实需要保护非引用类型的值的时候都挺适用的。如果是引用类型的值的话,可能会起不到保护作用。因为我们修改的往往是这个值引用的那个底层值,而 atomic.Value 只会保护这个值本身。 例子的话...比如保护全局配置、同时保护一坨全局计数、保护 bit array,等等。

    2020-01-18
    16
  • rename
    我认为最重要的三点是 要操作的变量类型,操作频率和整体操作耗时。请郝大指教~

    作者回复: 其实主要还是变量类型,原子操作在这块是严格的。能用原子就用原子。锁这个原语还是相对较重。

    2018-10-27
    14
  • 蜉蝣
    老师好,衍生问题 4 的回答不能明白。既然原子操作是不会被中断的,那么为什么还会出现 “如果写操作还没有进行完,读操作就来读了,那么就只能读到仅修改了一部分的值。” 这种写操作还没完进行完就有其他操作进来? 如果是因为多核多 CPU 的话,那是不是说,读的原子操作与写的原子操作是互斥?

    作者回复: 原文中的“如果写操作还没有进行完,读操作就来读了,那么就只能读到仅修改了一部分的值。”这句话是对读写锁来说的。如果只锁写操作、不锁读操作就会发生这样的问题。 这种原子操作一般都是通过原生的CPU指令实现的,所以从底层保障了并发操作的安全性。即使CPU是多核的,甚至有多个CPU,只要主板等硬件方面没有问题,都是可以保障的。 最后,原子操作和互斥锁不是一个东西。原子操作用防中断的方式来保并发,而互斥锁用串行化多个操作的方式来保并发(它不防中断)。 你如果说的只是“互斥”这个词,那你也可以理解为针对同一个变量的原子操作是“互斥”的(但它是通过“防中断”来达到“互斥”的目的)。

    2020-11-06
    2
    8
  • mkii
    一旦atomic.Value类型的值(以下简称原子值)被真正使用,它就不应该再被复制了。 老师,如果Value存储的是引用类型,被复制有可能绕过原子值进行非并发操作这个我可以理解。但如果Value存储的是值类型,如果复制了再对这个副本操作应该不会对原值有影响。这里是怕别人误操作产生歧义吗?(即,我明明通过copyValue.store改了值,但为什么没生效?)

    作者回复: Value里面存储的是被操作值的指针啊,所以一样的。

    2021-02-25
    3
    2
  • poettian
    看到衍生问题4我也有个疑惑,那如果不用原子操作,是不是说假如在写的同时有读的操作,我们读取变量实际读到的是个不完整的值?

    作者回复: 是有这个可能的。

    2021-01-10
    2
  • noisyes
    通过一个或多个公开的函数,让外界间接地使用到它。这种情况下不要把原子值传递到外界,不论是传递原子值本身还是它的指针值。 但是即使编写了一个函数,但是不传递原子值的话,怎么才可以让外界使用它呢。

    作者回复: 靠复制啊,如果是值类型的值的话,从函数返回时就自动复制了,如果是引用类型的值的话,比如切片或字典,就需要手动复制。使用atomic.Value的时候一定要注意这类问题。

    2022-06-23
    1
  • 风翱
    老师,关于热更有什么方案呢? 是否有使用过,并在实际的项目中运用过? 有没有相应的事例?

    作者回复: 热更新的话,现在肯定是首选 context 啊,因为标准库中的其他包已经与 context 融合得非常好了。虽然现在市面上还有一些相关的第三方库,但是如果没有特殊需求的话,还还是推荐 context。

    2022-06-18
    3
    1
  • jxs1211
    go的内置函数的源码是在哪里可以找到,比如说append()使用时,都做了什么

    作者回复: 要看切片相关的话,先在源码 src 目录里找runtime/slice.go 。这个文件里有个 growslice 函数,append 会调用它。后边你就顺藤摸瓜吧。

    2021-10-24
    1
  • lesserror
    郝林老师,demo64 中的 示例3 的第二条 打印: fmt.Printf("Store %d to box2.\n", v3) 是不是 应该 改为: fmt.Printf("Store %d to box3.\n", v3)

    作者回复: 好像应该是 box3 ,回头我改一下吧,谢谢

    2021-08-17
    1
  • Da Vinci
    在首次真正使用后,原子值就不应该再被复制了,这句话不是特别理解,想请老师再解释一下

    作者回复: Value 类型的值里面一旦存了值就不应该再拷贝了,因为这很可能会让基于内存地址的互斥机制失效,并产生混乱。这也是 Value 的文档里特别说明的。

    2019-11-11
收起评论
显示
设置
留言
18
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部