Golang 语言标准库 sync/atomic 包原子操作

简介: Golang 语言标准库 sync/atomic 包原子操作

01

介绍


我们已经介绍过 Mutex、RWMutex 等并发原语操作,如果您还没有阅读,请查看文末「推荐阅读」列表。


本文我们介绍 sync/atomic 包提供的原子操作的方法,相比并发原语操作,使用原子操作会更轻量。


我们知道,相同代码在不同 CPU 架构中编译的结果可能不同,sync/atomic 包提供的原子操作的方法帮我们解决了这个问题,所以如果您想保证原子操作,一定要使用 sync/atomic 包提供的原子操作的方法。


02

方法


因为 Go (1.15)目前还没有支持泛型,所以每个 sync/atomic 包提供的原子操作的方法,都包含不同类型的同名方法。但是不同类型的同名方法除了类型不同,使用方法是相同的。


细心的读者可能发现,为什么 Add 方法只支持 5 种类型,其它方法都支持 6 种类型,因为在 Go 语言中,指针是不能运算的,Add 方法是修改操作,其它方法否是存取操作。限于篇幅,我们重点介绍 Add 方法。


Add 方法:


func AddInt32(addr *int32, delta int32) (new int32)
func AddUint32(addr *uint32, delta uint32) (new uint32)
func AddInt64(addr *int64, delta int64) (new int64)
func AddUint64(addr *uint64, delta uint64) (new uint64)
func AddUintptr(addr *uintptr, delta uintptr) (new uintptr


Add 方法的功能就是给参数 1 的地址中的值,加上参数 2 的 delta 的值。如果类型是有符号类型,参数 2 可以是负值,用于原子减法运算;如果类型是无符号类型,参数 2 不可以是负值,此时分两种情况:

  • 情况 1 是参数 2 是一个无符号类型的变量 v,-v 在 GO 语言中是合法的。
  • 情况 2 是参数 2 是一个正整数常量 c,-c 在 Go 语言中是非法的,此时我们可以使用 ^T(c-1) 作为参数 2 的值。

如果 ^T(c-1) 中的 c 是一个类型确定的值,可以简化为 ^(c-1)。


示例代码:


func main() {
  // 多个 goroutine 的原子计数器
  var counter uint64
  var wg sync.WaitGroup
  for i := 0; i < 50; i++ {
    wg.Add(1)
    go func() {
      for j := 0; j < 100000; j++ {
        // 相加
        atomic.AddUint64(&counter, 1)
      }
      wg.Done()
    }()
  }
  wg.Wait()
  fmt.Println("counter:", counter)
}


如果您感兴趣的话,可以将示例代码中第 10 行代码替换为 counter++,运行程序并查看运行结果是否会有不同。


Swap 方法:


func SwapInt32(addr *int32, new int32) (old int32)
func SwapInt64(addr *int64, new int64) (old int64)
func SwapUint32(addr *uint32, new uint32) (old uint32)
func SwapUint64(addr *uint64, new uint64) (old uint64)
func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)
func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)


Swap 方法的功能是原值替换为新值,并返回原值。


CompareAndSwap 方法:


func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)
func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)
func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)


CompareAndSwap 方法的功能是比较原值,符合条件则将原值替换为新值。


Load 方法:


func LoadInt32(addr *int32) (val int32)
func LoadInt64(addr *int64) (val int64)
func LoadUint32(addr *uint32) (val uint32)
func LoadUint64(addr *uint64) (val uint64)
func LoadUintptr(addr *uintptr) (val uintptr)
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)


Load 方法的功能是取值。


Store 方法:


func StoreInt32(addr *int32, val int32)
func StoreInt64(addr *int64, val int64)
func StoreUint32(addr *uint32, val uint32)
func StoreUint64(addr *uint64, val uint64)
func StoreUintptr(addr *uintptr, val uintptr)
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)


Store 方法的功能是存值。


03

value 类型


细心的读者可能已经发现,上述 5 种方法,都是针对特定类型的方法。sync/atomic 还有一个 value 类型,它有两个方法,分别是 Load 和 Store,用于读取和存储任意类型的值,它没有 Add 和 Swap。


func (v *Value) Load() (x interface{})
func (v *Value) Store(x interface{})


Load 方法的返回结果类型和 Store 方法的参数类型都是空接口类型 interface{},所以 Store 方法的参数可以接收任意类型的值。


需要注意的是,*Value 类型的 v,只要调用过一次 Store 方法,那么传入此方法的后续参数类型必须和之前传入参数的类型一致,否则会导致 panic。


04

总结


本文主要介绍了 sync/atomic 包的原子操作的方法的基本使用,其中重点介绍了 Add 方法和 Value 类型的方法,读者朋友可以根据实际场景选用最合适的方法。





目录
相关文章
|
8月前
|
Java 编译器 Go
【Golang】(1)Go的运行流程步骤与包的概念
初次上手Go语言!先来了解它的运行流程吧! 在Go中对包的概念又有怎样不同的见解呢?
416 4
|
11月前
|
设计模式 Kubernetes Go
​​什么是Golang项目的“主包精简,逻辑外置”?​
“主包精简,逻辑外置”是Go语言项目的一种设计原则,强调将程序入口保持简单,核心逻辑拆分至其他包,以提升代码可维护性、可测试性及扩展性,适用于CLI工具、Web服务等场景。
266 7
|
Go
在golang中发起http请求以获取访问域名的ip地址实例(使用net, httptrace库)
这只是追踪我们的行程的简单方法,不过希望你跟着探险家的脚步,即使是在互联网的隧道中,也可以找到你想去的地方。接下来就是你的探险之旅了,祝你好运!
652 26
|
Go 开发者
go-carbon v2.6.0 重大版本更新,轻量级、语义化、对开发者友好的 golang 时间处理库
carbon 是一个轻量级、语义化、对开发者友好的 Golang 时间处理库,提供了对时间穿越、时间差值、时间极值、时间判断、星座、星座、农历、儒略日 / 简化儒略日、波斯历 / 伊朗历的支持
341 3
|
网络协议 测试技术 Linux
Golang 实现轻量、快速的基于 Reactor 模式的非阻塞 TCP 网络库
gev 是一个基于 epoll 和 kqueue 实现的高性能事件循环库,适用于 Linux 和 macOS(Windows 暂不支持)。它支持多核多线程、动态扩容的 Ring Buffer 读写缓冲区、异步读写和 SO_REUSEPORT 端口重用。gev 使用少量 goroutine,监听连接并处理读写事件。性能测试显示其在不同配置下表现优异。安装命令:`go get -u github.com/Allenxuxu/gev`。
424 0
|
JSON Go 开发者
go-carbon v2.5.0 发布,轻量级、语义化、对开发者友好的 golang 时间处理库
carbon 是一个轻量级、语义化、对开发者友好的 Golang 时间处理库,提供了对时间穿越、时间差值、时间极值、时间判断、星座、星座、农历、儒略日 / 简化儒略日、波斯历 / 伊朗历的支持。
379 4
|
存储 Cloud Native Shell
go库介绍:Golang中的Viper库
Viper 是 Golang 中的一个强大配置管理库,支持环境变量、命令行参数、远程配置等多种配置来源。本文详细介绍了 Viper 的核心特点、应用场景及使用方法,并通过示例展示了其强大功能。无论是简单的 CLI 工具还是复杂的分布式系统,Viper 都能提供优雅的配置管理方案。
544 6
|
Unix Go
Golang语言标准库time之日期和时间相关函数
这篇文章是关于Go语言日期和时间处理的文章,介绍了如何使用Go标准库中的time包来处理日期和时间。
849 3
|
8月前
|
存储 安全 Java
【Golang】(4)Go里面的指针如何?函数与方法怎么不一样?带你了解Go不同于其他高级语言的语法
结构体可以存储一组不同类型的数据,是一种符合类型。Go抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go并非是一个传统OOP的语言,但是Go依旧有着OOP的影子,通过结构体和方法也可以模拟出一个类。
411 2
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
847 4
Golang语言之管道channel快速入门篇