zoukankan      html  css  js  c++  java
  • go语言标准库sync/atomic中的原子操作

    原子操作吧其他同步技术更底层。他们没有锁,基本是在硬件层面实现的。事实上,他们经常被用来实现其他同步技术。

    请注意,下面的许多例子并发并发编程。他们仅用于来展示如何使用标准库中的sync/atomic包中的原子函数。

    go语言中的原子操作概览

    标准库中的sync/atomic对整数类型T(包含int32,int64,uint32,uint64,uintptr)提供5种类型的原子函数。

    func AddT(addr *T, delta T)(new T)
    func LoadT(addr *T) (val T)
    func StoreT(addr *T, val T)
    func SwapT(addr *T, new T) (old T)
    func CompareAndSwapT(addr *T, old, new T) (swapped bool)
    

    下面是in32的

    func AddInt32(addr *int32, delta int32)(new int32)
    func LoadInt32(addr *int32) (val int32)
    func StoreInt32(addr *int32, val int32)
    func SwapInt32(addr *int32, new int32) (old int32)
    func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
    

    为(安全)指针类型提供以下四个原子函数:

    func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
    func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
    func SwapPointer(addr *unsafe.Pointer, new T) (old unsafe.Pointer)
    func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)
    

    上面并没有AddPointer函数,因为go的指针不支持算术运算
    sync/atomic还支持一个类型Value. 它关联到指针类型*Value,有两个函数LoadStore,一个Value可以被用来原子的加载或者存储任何类型 的值

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

    本文的其余部分显示了有关如何使用Go中提供的原子操作的一些示例。

    整数的原子操作

    以下示例说明如何使用AddInt32函数对int32值执行add atomic操作.在此示例中,主goroutine创建了1000个新的并发goroutine。每个新创建的goroutine将整数n增加1。原子操作保证了这些goroutine之间没有数据竞争。最后,保证打印1000。

    package main
    
    import (
        "fmt"
        "sync"
        "sync/atomic"
    )
    
    func main() {
        var n int32
        var wg sync.WaitGroup
        for i := 0; i < 1000; i++ {
            wg.Add(1)
            go func() {
                atomic.AddInt32(&n, 1)
                wg.Done()
            }()
        }
        wg.Wait()
    
        fmt.Println(atomic.LoadInt32(&n)) // 1000
    }
    

    如果需要同时使用类型的值,则StoreT和LoadT原子函数通常用于实现类型(相应指针类型)的setter和getter方法。例如,

    type Page struct {
        views uint32
    }
    
    func (page *Page) SetViews(n uint32) {
        atomic.StoreUint32(&page.views, n)
    }
    
    func (page *Page) Views() uint32 {
        return atomic.LoadUint32(&page.views)
    }
    

    对于有符号整数类型T(int32或int64),调用AddT函数的第二个参数可以是负值,以执行原子减操作。但是如何对无符号累着执行原子减操作呢。对第二个无符号整型参数有两个中情况。

    1. 对无符号变量v, -v是合法的。所以我们把-v传给AddT的第二个参数。
    2. 对应正常量整数c,-c作为AddT的第二个参数是非法的,我们可以使用^T(c-1)作为AddT的第二个参数
      例子如下
    package main
    
    import (
        "fmt"
        "sync/atomic"
    )
    
    func main() {
        var (
            n uint64 = 97
            m uint64 = 1
            k int    = 2
        )
        const (
            a        = 3
            b uint64 = 4
            c uint32 = 5
            d int    = 6
        )
    
        atomic.AddUint64(&n, -m);             fmt.Println(n) // 96 (97 - 1)
        atomic.AddUint64(&n, -uint64(k));     fmt.Println(n) // 94 (95 - 2)
        atomic.AddUint64(&n, ^uint64(a - 1)); fmt.Println(n) // 91 (94 - 3)
        atomic.AddUint64(&n, ^(b - 1));       fmt.Println(n) // 87 (91 - 4)
        atomic.AddUint64(&n, ^uint64(c - 1)); fmt.Println(n) // 82 (87 - 5)
        atomic.AddUint64(&n, ^uint64(d - 1)); fmt.Println(n) // 76 (82 - 6)
        x := b; atomic.AddUint64(&n, -x);     fmt.Println(n) // 72 (76 - 4)
        atomic.AddUint64(&n, ^(m - 1));       fmt.Println(n) // 71 (72 - 1)
        atomic.AddUint64(&n, ^uint64(k - 1)); fmt.Println(n) // 69 (71 - 2)
    }
    

    一个SwapT函数调用与StoreT调用想像,但是返回原来的值。
    CompareAndSwapT函数调用仅仅适用于当前值等于传入的旧值的时候存储新值。返回的bool值用来表示存储操作是否被执行。

    package main
    
    import (
        "fmt"
        "sync/atomic"
    )
    
    func main() {
        var n int64 = 123
        var old = atomic.SwapInt64(&n, 789)
        fmt.Println(n, old) // 789 123
        fmt.Println(atomic.CompareAndSwapInt64(&n, 123, 456)) // false
        fmt.Println(n) // 789
        fmt.Println(atomic.CompareAndSwapInt64(&n, 789, 456)) // true
        fmt.Println(n) // 456
    }
    

    请注意,到目前为止(Go 1.12),64位字,a.k.a。,int64和uint64值的原子操作要求64位字必须在内存中对齐8字节.

    指针的原子操作

    前文我们有提到对于指针的原子操作,标准库中提供的四个函数。
    在go中,任何指针类型可以被转换为unsafe.Pointer,反之亦然。所以,*unsafe.Pointer类型也能被明确的转换成unsafe.Pointer类型,反之亦然。
    下面的例子展示如何使用原子操作操作指针

    package main
    
    import (
        "fmt"
        "sync/atomic"
        "unsafe"
    )
    
    type T struct {a, b, c int}
    var pT *T
    
    func main() {
        var unsafePPT = (*unsafe.Pointer)(unsafe.Pointer(&pT))
        var ta, tb T
        // store
        atomic.StorePointer(unsafePPT, unsafe.Pointer(&ta))
        // load
        pa1 := (*T)(atomic.LoadPointer(unsafePPT))
        fmt.Println(pa1 == &ta) // true
        // swap
        pa2 := atomic.SwapPointer(unsafePPT, unsafe.Pointer(&tb))
        fmt.Println((*T)(pa2) == &ta) // true
        // compare and swap
        b := atomic.CompareAndSwapPointer(unsafePPT, pa2, unsafe.Pointer(&tb))
        fmt.Println(b) // false
        b = atomic.CompareAndSwapPointer(unsafePPT, unsafe.Pointer(&tb), pa2)
        fmt.Println(b) // true
    }
    

    是的,使用指针原子函数非常冗长。事实上,不仅用途冗长,它们也不受Go 1兼容性指南的保护,因为这些用途需要导入不安全的标准包。

    任意类型值的原子操作

    同步/原子标准包中提供的值类型可用于原子加载和存储任何类型的值。Type * Value有两种方法,Load和Store。添加和交换方法不适用于* Value类型.
    Load和Store的参数都是interface{}。所以调用时Store可以使用任何类型作为参数,但是对于可寻址的Value 值v,一旦v.Store被调用过,接下来的v.Store必须采用同样类型的参数作为参数,否则将会panic。用nil作为参数也会导致panic。
    例子:

    package main
    
    import (
        "fmt"
        "sync/atomic"
    )
    
    func main() {
        type T struct {a, b, c int}
        var ta = T{1, 2, 3}
        var v atomic.Value
        v.Store(ta)
        var tb = v.Load().(T)
        fmt.Println(tb)       // {1 2 3}
        fmt.Println(ta == tb) // true
    
        v.Store("hello") // will panic
    }



  • 相关阅读:
    二次冲刺站立会议五
    二次冲刺站立会议四
    二次冲刺站立会议三
    二次冲刺站立会议二
    自己常用网址导航
    Eclipse几个配置
    JVM参数和jvm.log
    JVM性能监控命令
    Linux基本信息查看命令
    Linux IO性能监控工具
  • 原文地址:https://www.cnblogs.com/ExMan/p/12397228.html
Copyright © 2011-2022 走看看