原子操作吧其他同步技术更底层。他们没有锁,基本是在硬件层面实现的。事实上,他们经常被用来实现其他同步技术。
请注意,下面的许多例子并发并发编程。他们仅用于来展示如何使用标准库中的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,有两个函数Load
和Store
,一个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))
如果需要同时使用类型的值,则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函数的第二个参数可以是负值,以执行原子减操作。但是如何对无符号累着执行原子减操作呢。对第二个无符号整型参数有两个中情况。
对无符号变量v, -v是合法的。所以我们把-v传给AddT的第二个参数。
对应正常量整数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)
一个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)
请注意,到目前为止(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
是的,使用指针原子函数非常冗长。事实上,不仅用途冗长,它们也不受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)
请发表评论