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 类型的方法,读者朋友可以根据实际场景选用最合适的方法。





目录
相关文章
|
27天前
|
JSON Go 开发者
go-carbon v2.5.0 发布,轻量级、语义化、对开发者友好的 golang 时间处理库
carbon 是一个轻量级、语义化、对开发者友好的 Golang 时间处理库,提供了对时间穿越、时间差值、时间极值、时间判断、星座、星座、农历、儒略日 / 简化儒略日、波斯历 / 伊朗历的支持。
37 4
|
1月前
|
存储 Cloud Native Shell
go库介绍:Golang中的Viper库
Viper 是 Golang 中的一个强大配置管理库,支持环境变量、命令行参数、远程配置等多种配置来源。本文详细介绍了 Viper 的核心特点、应用场景及使用方法,并通过示例展示了其强大功能。无论是简单的 CLI 工具还是复杂的分布式系统,Viper 都能提供优雅的配置管理方案。
|
3月前
|
Prometheus Cloud Native Go
Golang语言之Prometheus的日志模块使用案例
这篇文章是关于如何在Golang语言项目中使用Prometheus的日志模块的案例,包括源代码编写、编译和测试步骤。
79 3
Golang语言之Prometheus的日志模块使用案例
|
2月前
|
前端开发 中间件 Go
实践Golang语言N层应用架构
【10月更文挑战第2天】本文介绍了如何在Go语言中使用Gin框架实现N层体系结构,借鉴了J2EE平台的多层分布式应用程序模型。文章首先概述了N层体系结构的基本概念,接着详细列出了Go语言中对应的构件名称,包括前端框架(如Vue.js、React)、Gin的处理函数和中间件、依赖注入和配置管理、会话管理和ORM库(如gorm或ent)。最后,提供了具体的代码示例,展示了如何实现HTTP请求处理、会话管理和数据库操作。
38 0
|
3月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
140 4
Golang语言之管道channel快速入门篇
|
3月前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
71 4
Golang语言文件操作快速入门篇
|
3月前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
117 3
Golang语言之gRPC程序设计示例
|
3月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
100 4
|
3月前
|
Go
Golang语言错误处理机制
这篇文章是关于Golang语言错误处理机制的教程,介绍了使用defer结合recover捕获错误、基于errors.New自定义错误以及使用panic抛出自定义错误的方法。
55 3
|
3月前
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
73 4
Golang语言goroutine协程篇