千家信息网

Golang提供了哪些原子性操作

发表于:2026-11-04 作者:千家信息网编辑
千家信息网最后更新 2026年11月04日,这篇文章主要为大家展示了"Golang提供了哪些原子性操作",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"Golang提供了哪些原子性操作"这篇文章吧。Go
千家信息网最后更新 2026年11月04日Golang提供了哪些原子性操作

这篇文章主要为大家展示了"Golang提供了哪些原子性操作",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"Golang提供了哪些原子性操作"这篇文章吧。

Go 语言提供了哪些原子操作

Go语言通过内置包sync/atomic提供了对原子操作的支持,其提供的原子操作有以下几大类:

  • 增减,操作方法的命名方式为AddXXXType,保证对操作数进行原子的增减,支持的类型为int32、int64、uint32、uint64、uintptr,使用时以实际类型替换前面我说的XXXType就是对应的操作方法。

  • 载入,保证了读取到操作数前没有其他任务对它进行变更,操作方法的命名方式为LoadXXXType,支持的类型除了基础类型外还支持Pointer,也就是支持载入任何类型的指针。

  • 存储,有载入了就必然有存储操作,这类操作的方法名以Store开头,支持的类型跟载入操作支持的那些一样。

  • 比较并交换,也就是CAS (Compare And Swap),像Go的很多并发原语实现就是依赖的CAS操作,同样是支持上面列的那些类型。

  • 交换,这个简单粗暴一些,不比较直接交换,这个操作很少会用。

互斥锁跟原子操作的区别

平日里,在并发编程里,Go语言sync包里的同步原语Mutex是我们经常用来保证并发安全的,那么他跟atomic包里的这些操作有啥区别呢?在我看来他们在使用目的和底层实现上都不一样:

  • 使用目的:互斥锁是用来保护一段逻辑,原子操作用于对一个变量的更新保护。

  • 底层实现:Mutex由操作系统的调度器实现,而atomic包中的原子操作则由底层硬件指令直接提供支持,这些指令在执行的过程中是不允许中断的,因此原子操作可以在lock-free的情况下保证并发安全,并且它的性能也能做到随CPU个数的增多而线性扩展。

对于一个变量更新的保护,原子操作通常会更有效率,并且更能利用计算机多核的优势。

比如下面这个,使用互斥锁的并发计数器程序:

func mutexAdd() { var a int32 =  0 var wg sync.WaitGroup var mu sync.Mutex start := time.Now() for i := 0; i < 100000000; i++ {  wg.Add(1)  go func() {   defer wg.Done()   mu.Lock()   a += 1   mu.Unlock()  }() } wg.Wait() timeSpends := time.Now().Sub(start).Nanoseconds() fmt.Printf("use mutex a is %d, spend time: %v\n", a, timeSpends)}

把Mutex改成用方法atomic.AddInt32(&a, 1)调用,在不加锁的情况下仍然能确保对变量递增的并发安全。

func AtomicAdd() { var a int32 =  0 var wg sync.WaitGroup start := time.Now() for i := 0; i < 1000000; i++ {  wg.Add(1)  go func() {   defer wg.Done()   atomic.AddInt32(&a, 1)  }() } wg.Wait() timeSpends := time.Now().Sub(start).Nanoseconds() fmt.Printf("use atomic a is %d, spend time: %v\n", atomic.LoadInt32(&a), timeSpends)}

可以在本地运行以上这两段代码,可以观察到计数器的结果都最后都是1000000,都是线程安全的。
需要注意的是,所有原子操作方法的被操作数形参必须是指针类型,通过指针变量可以获取被操作数在内存中的地址,从而施加特殊的CPU指令,确保同一时间只有一个goroutine能够进行操作。
上面的例子除了增加操作外我们还演示了载入操作,接下来我们来看一下CAS操作。

比较并交换

该操作简称CAS (Compare And Swap)。 这类操作的前缀为 CompareAndSwap :

func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)

该操作在进行交换前首先确保被操作数的值未被更改,即仍然保存着参数 old 所记录的值,满足此前提条件下才进行交换操作。CAS的做法类似操作数据库时常见的乐观锁机制。

需要注意的是,当有大量的goroutine 对变量进行读写操作时,可能导致CAS操作无法成功,这时可以利用for循环多次尝试。

上面我只列出了比较典型的int32和unsafe.Pointer类型的CAS方法,主要是想说除了读数值类型进行比较交换,还支持对指针进行比较交换。

unsafe.Pointer提供了绕过Go语言指针类型限制的方法,unsafe指的并不是说不安全,而是说官方并不保证向后兼容。

// 定义一个struct类型Ptype P struct{ x, y, z int }  // 执行类型P的指针var pP *P  func main() {      // 定义一个执行unsafe.Pointer值的指针变量    var unsafe1 = (*unsafe.Pointer)(unsafe.Pointer(&pP))      // Old pointer    var sy P      // 为了演示效果先将unsafe1设置成Old Pointer    px := atomic.SwapPointer(        unsafe1, unsafe.Pointer(&sy))      // 执行CAS操作,交换成功,结果返回true    y := atomic.CompareAndSwapPointer(        unsafe1, unsafe.Pointer(&sy), px)      fmt.Println(y)}

上面的示例并不是在并发环境下进行的CAS,只是为了演示效果,先把被操作数设置成了Old Pointer。
其实Mutex的底层实现也是依赖原子操作中的CAS实现的,原子操作的atomic包相当于是sync包里的那些同步原语的实现依赖。

比如互斥锁Mutex的结构里有一个state字段,其是表示锁状态的状态位。

type Mutex struct { state int32 sema  uint32}

为了方便理解,我们在这里将它的状态定义为0和1,0代表目前该锁空闲,1代表已被加锁,以下是sync.Mutex中Lock方法的部分实现代码。

func (m *Mutex) Lock() {   // Fast path: grab unlocked mutex.   if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {       if race.Enabled {           race.Acquire(unsafe.Pointer(m))       }       return   }   // Slow path (outlined so that the fast path can be inlined)    m.lockSlow()}

在atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked)中,m.state代表锁的状态,通过CAS方法,判断锁此时的状态是否空闲(m.state==0),是,则对其加锁(mutexLocked常量的值为1)。

atomic.Value保证任意值的读写安全

atomic包里提供了一套Store开头的方法,用来保证各种类型变量的并发写安全,避免其他操作读到了修改变量过程中的脏数据。

func StoreInt32(addr *int32, val int32)func StoreInt64(addr *int64, val int64)func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer) ...

这些操作方法的定义与上面介绍的那些操作的方法类似,我就不再演示怎么使用这些方法了。

值得一提的是如果你想要并发安全的设置一个结构体的多个字段,除了把结构体转换为指针,通过StorePointer设置外,还可以使用atomic包后来引入的atomic.Value,它在底层为我们完成了从具体指针类型到unsafe.Pointer之间的转换。

有了atomic.Value后,它使得我们可以不依赖于不保证兼容性的unsafe.Pointer类型,同时又能将任意数据类型的读写操作封装成原子性操作(中间状态对外不可见)。

atomic.Value类型对外暴露了两个方法:

  • v.Store(c) - 写操作,将原始的变量c存放到一个atomic.Value类型的v里。

  • c := v.Load() - 读操作,从线程安全的v中读取上一步存放的内容。

1.17 版本我看还增加了Swap和CompareAndSwap方法。

简洁的接口使得它的使用也很简单,只需将需要做并发保护的变量读取和赋值操作用Load()和Store()代替就行了。
由于Load()返回的是一个interface{}类型,所以在使用前我们记得要先转换成具体类型的值,再使用。下面是一个简单的

例子演示atomic.Value的用法。

type Rectangle struct { length int width  int}var rect atomic.Valuefunc update(width, length int) { rectLocal := new(Rectangle) rectLocal.width = width rectLocal.length = length rect.Store(rectLocal)}func main() { wg := sync.WaitGroup{} wg.Add(10) // 10 个协程并发更新 for i := 0; i < 10; i++ {  go func() {   defer wg.Done()   update(i, i+5)  }() } wg.Wait() _r := rect.Load().(*Rectangle) fmt.Printf("rect.width=%d\nrect.length=%d\n", _r.width, _r.length)}

你也可以试试,不用atomic.Value,直接给Rectange类型的指针变量赋值,看看在并发条件下,两个字段的值是不是能跟预期的一样变成10和15。

以上是"Golang提供了哪些原子性操作"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

类型 原子 方法 变量 指针 支持 安全 保证 操作数 状态 底层 演示 内容 语言 保护 代表 原语 字段 指令 数据 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 云南咖啡 互联网科技海报 博彦科技互联网金融 互联网是第几次科技革命的成果 互联网科技产业 中关村互联网科技集团 互联网的科技 疫情对互联网科技公司影响 互联网科技的发展 致聘互联网科技 互联网金融科技平台 无锡溥仪互联网科技有限公司 中国最先进互联网科技公司 速维有品互联网信息科技(苏州)有限公司 广州市恰铎互联网科技有限公司 国大互联网教育科技有限公司薪资 广州百诚互联网科技有限公司官网 互联网科技年终奖 郑州欣桑互联网科技有限公司 中国主要互联网科技公司 天津市摩尔互联网科技 无锡溥仪互联网科技有限公司 中盈科技互联网信息有限公司 汽车互联网创新科技园 山东星卡互联网科技有限公司官网 北京市互联网新科技 卡尔迅互联网科技有限公司 互联网界的黑科技 保山浩联互联网科技有限公司 互联网 科技节活动 微视频大赛
0