原子操作

原子操作 (Atomic)

什么是原子操作?

原子操作是一种不可分割的操作,要么全部执行成功,要么全部执行失败。在针对内存中某个值的操作时,原子操作可以确保在并发环境下不会出现数据竞争,其他 goroutine 无法在操作进行中对该值进行读写。

在 Golang 中, 针对原子操作,我们可以使用 sync/atomic 包提供的原子操作函数。这些函数可以确保在并发环境下对共享资源进行安全的读写。

注意

实际上,原子操作也可以通过加锁来实现,但是加锁操作涉及到内核态的上下文切换,会有比较大的性能消耗,而原子操作是在用户态完成的,性能更高,效率可能相差几倍。

互斥锁和原子操作的区别

  • 互斥锁通常用于保护一段代码,只有一个 goroutine 可以访问这段代码,其他 goroutine 需要等待, 而原子操作通常用于保护一个变量,确保在并发环境下对变量的读写是安全的。

  • 互斥锁是一种悲观锁,它认为并发访问是一种常态,所以会在访问前先加锁,而原子操作是一种乐观锁,它认为并发访问是一种特例,所以会先尝试进行操作,如果失败再进行重试。

  • 互斥锁是一种重量级锁,它会涉及到内核态的上下文切换,性能消耗较大,而原子操作是一种轻量级锁,它是在用户态完成的,性能更高。

  • 互斥锁有操作系统的调度器实现, 而原子操作则是有硬件提供的原子指令实现,无需加锁而实现并发安全。

实现原理

原子操作函数

操作
函数

读取

LoadInt32, LoadInt64, LoadUint32, LoadUint64, LoadPointer, LoadUintptr

写入

StoreInt32, StoreInt64, StoreUint32, StoreUint64, StorePointer, StoreUintptr

交换

SwapInt32, SwapInt64, SwapUint32, SwapUint64, SwapPointer, SwapUintptr

比较并交换

CompareAndSwapInt32, CompareAndSwapInt64, CompareAndSwapUint32, CompareAndSwapUint64, CompareAndSwapPointer, CompareAndSwapUintptr

增减

AddInt32, AddInt64, AddUint32, AddUint64, AddUintptr

效率对比

我们以累加到 10000 为例,看下加锁和原子操作的效率对比

  • 不加锁且不使用原子操作

package main

import (
	"fmt"
	"sync"
	"time"
)

var count = 0


func main() {
	wg := sync.WaitGroup{}
	start := time.Now()
	for _ = range 10000 {
		wg.Add(1)
		go func() {
			count ++
			wg.Done()
		}()
	}

	wg.Wait()

	fmt.Printf("time cost: %v, count: %d", time.Since(start), count)
}

[!important] time cost: 2.5907ms, count: 9663 可以看到,由于没有加锁,导致 count 的值并没有累加到 10000

  • 加锁

package main

import (
	"fmt"
	"sync"
	"time"
)

var count = 0


func main() {
	wg := sync.WaitGroup{}
	lock := sync.Mutex{}
	start := time.Now()
	for _ = range 10000 {
		wg.Add(1)
		go func() {
			lock.Lock()
			count ++
			lock.Unlock()
			wg.Done()
		}()
	}

	wg.Wait()

	fmt.Printf("time cost: %v, count: %d", time.Since(start), count)
}

[!important] time cost: 3.2373ms, count: 10000 可以看到时间消耗为 3.2373 毫秒,累加值为 10000

  • 原子操作

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

var count int64 = 0

func main() {
	wg := sync.WaitGroup{}
	start := time.Now()
	for _ = range 10000 {
		wg.Add(1)
		go func() {
			atomic.AddInt64(&count, 1)
			wg.Done()
		}()
	}

	wg.Wait()

	fmt.Printf("time cost: %v, count: %d", time.Since(start), count)
}

[!important] time cost: 2.6217ms, count: 10000 可以看到时间消耗为 2.6217 毫秒,累加值为 10000

最后更新于