sync/atomic definition
Official document address: https://pkg.go.dev/sync/atomic@go1.18.1
The sync/atomic
package in the Go language standard library provides low-level atomic memory primitives for implementing synchronization algorithms. The essence is to encapsulate the atomic operation instructions provided by the underlying CPU into Go function.
Using the atomic operations provided by sync/atomic
can ensure that only one goroutine operates on the variable at any time, avoiding concurrency conflicts.
Use sync/atomic
need special care, Go official recommends to use sync/atomic
only in some low-level application scenarios, other scenarios suggest using channel
or sync
lock in the package.
Share memory by communicating; don't communicate by sharing memory.
sync/atomic
provides 5 types of atomic operations and 1 Value
type.
5 types of atomic operations
- swap operation:
SwapXXX
- compare-and-swap operation:
CompareAndSwapXXX
- add operation:
AddXXX
- load operation:
LoadXXX
- store operation:
StoreXXX
These types of atomic operations only support a few basic data types.
add操作的Addxxx
函数只支持int32
, int64
, uint32
, uint64
, uintptr
These 5 basic data types.
其它类型的操作函数只支持int32
, int64
, uint32
, uint64
, uintptr
, unsafe.Pointer
These 6 basic data types.
Value type
Since the above five types of atomic operations only support a few basic data types, in order to expand the scope of atomic operations, the Go team introduced a new type in the sync/atomic
package of version 1.4 Value
. Value
type can be used to read (Load) and modify (Store) values of any type .
Go 1.4版本的Value
类型Load
Store
2个方法,Go 1.17版本Value
类型新增了CompareAndSwap
and Swap
the 2 new methods.
sync/atomic practice
swap operation
swap操作支持int32
, int64
, uint32
, uint64
, uintptr
, unsafe.Pointer
These 6 basic data types correspond to 6 swap operation functions.
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)
The function implemented by the swap operation is to replace the value in the memory pointed to by the addr
pointer with the new value new
, and then return the old value old
, which is the following pseudo code Atomic implementation:
old = *addr
*addr = new
return old
Let's take SwapInt32
for example:
// swap.go
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var newValue int32 = 200
var dst int32 = 100
// 把dst的值替换为newValue
old := atomic.SwapInt32(&dst, newValue)
// 打印结果
fmt.Println("old value: ", old, " new value:", dst)
}
The execution result of the above program is as follows:
old value: 100 new value: 200
compare-and-swap operation
compare-and-swap(CAS)操作支持int32
, int64
, uint32
, uint64
, uintptr
, unsafe.Pointer
These 6 basic data types correspond to 6 compare-and-swap operation functions.
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)
The function implemented by the compare-and-swap operation is to first compare addr
whether the value in the memory pointed to by the pointer is the old value old
equal.
- If they are equal, replace the value in the memory pointed to by the
addr
pointer with the new valuenew
, and returntrue
, indicating that the operation is successful. - If they are not equal, directly return
false
, indicating that the operation failed.
The compare-and-swap operation is an atomic implementation of the following pseudocode:
if *addr == old {
*addr = new
return true
}
return false
Let's take CompareAndSwapInt32
as an example:
// compare-and-swap.go
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var dst int32 = 100
oldValue := atomic.LoadInt32(&dst)
var newValue int32 = 200
// 先比较dst的值和oldValue的值,如果相等,就把dst的值替换为newValue
swapped := atomic.CompareAndSwapInt32(&dst, oldValue, newValue)
// 打印结果
fmt.Printf("old value: %d, swapped value: %d, swapped success: %v\n", oldValue, dst, swapped)
}
The execution result of the above program is as follows:
old value: 100, swapped value: 200, swapped success: true
add operation
add操作支持int32
, int64
, uint32
, uint64
, uintptr
5种基本数据类型,对应There are 5 add operation functions.
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)
The function implemented by the add operation is to add the value in the memory pointed to by the addr
pointer and delta
, and then return the new value, which is the atomic implementation of the following pseudocode:
*addr += delta
return *addr
Let's take AddInt32
for example:
// add.go
package main
import (
"fmt"
"sync"
"sync/atomic"
)
var wg sync.WaitGroup
// 多个goroutine并发读写sum,有并发冲突,最终计算得到的sum值是不准确的
func test1() {
var sum int32 = 0
N := 100
wg.Add(N)
for i := 0; i < N; i++ {
go func(i int32) {
sum += i
wg.Done()
}(int32(i))
}
wg.Wait()
fmt.Println("func test1, sum=", sum)
}
// 使用原子操作计算sum,没有并发冲突,最终计算得到sum的值是准确的
func test2() {
var sum int32 = 0
N := 100
wg.Add(N)
for i := 0; i < N; i++ {
go func(i int32) {
atomic.AddInt32(&sum, i)
wg.Done()
}(int32(i))
}
wg.Wait()
fmt.Println("func test2, sum=", sum)
}
func main() {
test1()
test2()
}
The execution result of the above program is as follows:
func test1, sum= 4857
func test2, sum= 4950
Note : For the test1 function, the result you get when you run it locally may be different from mine, and this value is not a fixed value.
load operation
load操作支持int32
, int64
, uint32
, uint64
, uintptr
, unsafe.Pointer
These 6 basic data types correspond to 6 load operation functions.
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)
The function implemented by the load operation is to return the value in the memory pointed to by the addr
pointer, which is an atomic implementation of the following pseudocode:
return *addr
Let's take LoadInt32
as an example:
// load.go
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var sum int32 = 100
result := atomic.LoadInt32(&sum)
fmt.Println("result=", result)
}
The execution result of the above program is as follows:
result= 100
store operation
store操作支持int32
, int64
, uint32
, uint64
, uintptr
, unsafe.Pointer
These 6 basic data types correspond to 6 store operation functions.
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)
The function implemented by the store operation is to modify the value in the memory pointed to by the addr
pointer to val
, which is an atomic implementation of the following pseudocode:
*addr = val
Let's take StoreInt32
as an example:
// store.go
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var sum int32 = 100
var newValue int32 = 200
// 将sum的值修改为newValue
atomic.StoreInt32(&sum, newValue)
// 读取修改后的sum值
result := atomic.LoadInt32(&sum)
// 打印结果
fmt.Println("result=", result)
}
The execution result of the above program is as follows:
result= 200
Value type
The sync/atomic
package in the Go standard library provides the Value
type, which can be used to concurrently read and modify any type of value.
Value
types are defined as follows:
// A Value provides an atomic load and store of a consistently typed value.
// The zero value for a Value returns nil from Load.
// Once Store has been called, a Value must not be copied.
//
// A Value must not be copied after first use.
type Value struct {
v any
}
Value
类型有4个方法: CompareAndSwap
, Load
, Store
, Swap
,定义如下:
func (v *Value) CompareAndSwap(old, new any) (swapped bool)
func (v *Value) Load() (val any)
func (v *Value) Store(val any)
func (v *Value) Swap(new any) (old any)
Source code implementation: https://cs.opensource.google/go/go/+/refs/tags/go1.18.1:src/sync/atomic/value.go
The following is a specific example: To do concurrent read and write to the map[string][string]
type, in order to avoid locking, use the value
type to read and modify map[string][string]
.
package main
import (
"sync/atomic"
"time"
)
func loadConfig() map[string]string {
// 从数据库或者文件系统中读取配置信息,然后以map的形式存放在内存里
return make(map[string]string)
}
func requests() chan int {
// 将从外界中接收到的请求放入到channel里
return make(chan int)
}
func main() {
// config变量用来存放该服务的配置信息
var config atomic.Value
// 初始化时从别的地方加载配置文件,并存到config变量里
config.Store(loadConfig())
go func() {
// 每10秒钟定时拉取最新的配置信息,并且更新到config变量里
for {
time.Sleep(10 * time.Second)
// 对应于赋值操作 config = loadConfig()
config.Store(loadConfig())
}
}()
// 创建协程,每个工作协程都会根据它所读取到的最新的配置信息来处理请求
for i := 0; i < 10; i++ {
go func() {
for r := range requests() {
// 对应于取值操作 c := config
// 由于Load()返回的是一个interface{}类型,所以我们要先强制转换一下
c := config.Load().(map[string]string)
// 这里是根据配置信息处理请求的逻辑...
_, _ = r, c
}
}()
}
}
Summary and Notes
- Atomic operations are supported by the underlying CPU's atomic operation instructions.
- 5 kinds of atomic operations and
Value
official document address: https://pkg.go.dev/sync/atomic@go1.18.1 - CAS operation will have ABA problems
- For the 386 processor architecture, the 64-bit atomic operation functions use CPU instructions that are only supported by the Pentium MMX or later processor model. For non-Linux ARM processor architectures, 64-bit atomic operation functions use CPU instructions supported by ARMv6k core or newer processor models. For ARM, 386, and 32-bit MIPS processor architectures, the caller of an atomic operation must align the 64-bit memory of the 64-bit word that is being accessed atomically. The first word of variables or allocated structures, arrays and slices can be considered 64-bit aligned. (This piece involves memory alignment, and a topic will be explained in detail later)
open source address
Articles and sample code are open sourced on GitHub: Beginner, Intermediate, and Advanced Tutorials in Go .
Official account: coding advanced. Follow the official account to get the latest Go interview questions and technology stacks.
Personal website: Jincheng's Blog .
Zhihu: Wuji .
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。