atomic包的新变化

Russ Cox在去年的系列文章中,提到对atomic包的改变,并且开了一个issue供大家讨论,现在他提交的改变已经merge到master分支,Go 1.19就会包含这些改变。

你也可以使用gotip提前了解这些改变。

Russ Cox这次的提交只是对atomic补充了一些新的类型,这些类型是对基本类型(primitive type, 如bool、int32、int64、uint32、uint64、uintptr等)的一个包装,以便提供原子操作。

事实上,uber很早以前就提供类似的功能,也许你很早之前就使用过uber-go/atomic这个库。

Russ Cox实现的方式也类似,毕竟,这个为基本类型实现包装器的套路还是比较固定的,但是实现上略微有些不同。

  • Russ Cox在标准库中的实现
    • 嵌入_ noCopy,方便go vet等工具做data race检查
    • 代码可以进行inline,提高性能
  • Uber的实现
    • 嵌入_ nocmp,避免非原子比较
    • 提供JSON Marshal/UnMarshal的功能,方便序列化
    • 提供更多类型的包装器: DurationStringTimeFloat64Error

我们在这篇文章中主要介绍Russ Cox的实现,毕竟这是Go标准库中的改动,我多少要对这些改动有些印象,在我们将来的代码开发,或者看别人的代码时做到心中有数。

这次改动增加了bool、int32、int64、uint32、uint64、uintptr、Value、unsafe.Pointer类型的包装器:BoolInt32Int64Uint32Uint64UintptrValuePointer

所有这些类型都包含下面四个方法:

  • CompareAndSwap(old, new *T) (swapped bool) : 执行CAS操作
  • Load() *T : 原子load对应的值
  • Store(val *T) : 将值val原子存储
  • Swap(new T) (old T): 原子保存新值,并返回老的值

针对不同的类型,可能还会有一些额外的方法,比如:

  • Int32: func (x *Int32) Add(delta int32) (new int32)
  • Int64: func (x *Int64) Add(delta int64) (new int64)
  • Uint32: func (x *Int64) Add(delta int64) (new int64)
  • Uint64: func (x *Uint64) Add(delta uint64) (new uint64)
  • Uintptr: func (x *Uintptr) Add(delta uintptr) (new uintptr)

同样,针对Uint32Uint64类型,只有Add方法,如果想减去一个正值c,就得利用Add加上一个负值(-c),可是delta的类型都是uxxx类型,怎么办呢?使用^uint64(c-1)或者^uint32(c-1)

这些包装器和方法其实对应的是atomic的相应的函数,所以使用起来没什么难度:

1
2
3
4
5
6
var i Int64
i.Add(100)
i.Load()
i.Store(200)
i.Swap(300)
I.CompareAndSwap(300,400)

你可以查看go doc了解更详细的信息: sync/atomic