Golang sync_atomic元子操作

1. atomic介绍

sync/atomic包提供了原子操作的能力,直接有底层CPU硬件支持,因而一般要比基于操作系统API的锁方式效率高些;这些功能需要非常小心才能正确使用。 除特殊的底层应用程序外,同步更适合使用channel或sync包的功能。 通过消息共享内存; 不要通过共享内存进行通信。

原子操作是在执行中不能被中断的操作,通常由CPU芯片级能力来保证,并由操作系统提供调用,golang基于操作系统的能力,也提供了基于原子操作的支持。

1.1. 原子操作与临界区的区别

原子操作是能保证执行期间连续的,不会被中断

临界区只能保证访问共享数据是按顺序访问的,但不保证访问期间不会被切换context

1.2. golang提供的原子操作

sync/atomic是go语言提供的非侵入式原子操作包。

Go语言提供的原子操作都是非侵入式的。它们由标准库代码包sync/atomic中的众多函数代表。

我们可以通过调用sync/atomic这些函数对几种简单的类型的值进行原子操作。

这些类型包括int32、int64、uint32、uint64、uintptr和unsafe.Pointer类型,共6个。

这些函数提供的原子操作共有5种,即:增或减、比较并交换、载入、存储和交换。

1.3. 侵入式和非侵入式

侵入式和非侵入式,是从调用关系角度来说的。

比如模块A,调用了模块B中部分功能。
那么模块A是将模块B作为自己的一部分,还是将B作为一个独立的部分,然后再去调用。

如果将模块B作为模块A的一部分,就是侵入式;

如果将B作为一个独立的部分,则为非侵入式。侵入式,往往则意味着高耦合。

2. 操作包

2.1. 基础操作API

add、cas、load、store、swap

int32 int64 uint32 uint64 uintptr unsafe.Pointer Value

  • 原子操作函数的第一个参数都是传变量地址的,因为原子函数需要改变变量的值
  • 原子操作函数可以给add传入负数来做减法,计算机内部都是补码加法,没有减法
  • cas是带条件的swap,只有符合条件才swap

2.1.1. 自增和自减的操作API

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

2.1.2. 对于比较和交换的操作API

func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (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)

2.1.3. 载入操作API

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

2.1.4. 存储操作API

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

2.1.5. 交换操作API

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

2.2. atomic.Value

  • 支持原子性的load/store任意值
  • 一旦开始存一个值那么后续所有值的类型必须和第一个值的类型一致
  • 不支持nil
  • 不能copy,copy后就是另一个Value了
  • 不建议存储引用类型的值
  • 建议是私有变量,不要对外公开,更新需要严格检查条件

3. atomic.Value vs sync.Mutex

  • atomic.Value 简单、性能好,无死锁问题,但限制多
  • sync.Mutex 是通用的锁保护方案
  • 优先选择atomic.Value

4. 示例

package main

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

func main() {
    var count int32
    fmt.Println("main start...")
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            fmt.Println("goroutine:", i, "start...")
            atomic.AddInt32(&count, 1)

            fmt.Println("goroutine:", i, "count:", count, "end...")
        }(i)
    }
    wg.Wait()
    fmt.Println("main end...")
}

发表评论

邮箱地址不会被公开。 必填项已用*标注