sync/atomic
Standard Package
sync/atomic
standard package.
sync/atomic
standard package provides the following five atomic functions for an integer type T
, where T
must be any of int32
, int64
, uint32
, uint64
and uintptr
.
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)
int32
.
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)
unsafe.Pointer
(the Go counterpart of C void*
).
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)
func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer,
) (old unsafe.Pointer)
func CompareAndSwapPointer(addr *unsafe.Pointer,
old, new unsafe.Pointer) (swapped bool)
AddPointer
function for pointers, as Go (safe) pointers don't support arithmetic operations.
sync/atomic
standard package also provides a type Value
, which corresponding pointer type *Value
has four methods (listed below, the later two were introduced by Go 1.17). We may use these methods to do atomic operations for values of any type.
func (*Value) Load() (x interface{})
func (*Value) Store(x interface{})
func (*Value) Swap(new interface{}) (old interface{})
func (*Value) CompareAndSwap(old, new interface{}) (swapped bool)
Int32
, Int64
, Uint32
, Uint64
and Uintptr
are for integer atomic operations. The methods of the atomic.Int32
type are listed below. The methods of the other four types present in the similar way.
func (*Int32) Add(delta int32) (new int32)
func (*Int32) Load() int32
func (*Int32) Store(val int32)
func (*Int32) Swap(new int32) (old int32)
func (*Int32) CompareAndSwap(old, new int32) (swapped bool)
sync/atomic
package is one of these packages. The Pointer[T any]
type introudced in this package by Go 1.19 is a generic type. Its methods are listed below.
(*Pointer[T]) Load() *T
(*Pointer[T]) Store(val *T)
(*Pointer[T]) Swap(new *T) (old *T)
(*Pointer[T]) CompareAndSwap(old, new *T) (swapped bool)
Bool
type to do boolean atomic operations.
Add
atomic operation on an int32
value by using the AddInt32
function. In this example, 1000 new concurrent goroutines are created by the main goroutine. Each of the new created goroutine increases the integer n
by one. Atomic operations guarantee that there are no data races among these goroutines. In the end, 1000
is guaranteed to be printed.
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
}
atomic.AddInt32(&n, 1)
is replaced with n++
, then the output might be not 1000
.
atomic.Int32
type and its methods (since Go 1.19). This code looks a bit tidier.
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var n atomic.Int32
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
n.Add(1)
wg.Done()
}()
}
wg.Wait()
fmt.Println(n.Load()) // 1000
}
StoreT
and LoadT
atomic functions/methods are often used to implement the setter and getter methods of (the corresponding pointer type of) a type if the values of the type need to be used concurrently. For example, the function version:
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)
}
type Page struct {
views atomic.Uint32
}
func (page *Page) SetViews(n uint32) {
page.views.Store(n)
}
func (page *Page) Views() uint32 {
return page.views.Load()
}
T
(int32
or int64
), the second argument for a call to the AddT
function can be a negative value, to do an atomic decrease operation. But how to do atomic decrease operations for values of an unsigned type T
, such as uint32
, uint64
and uintptr
? There are two circumstances for the second unsigned arguments.
v
of type T
, -v
is legal in Go. So we can just pass -v
as the second argument of an AddT
call.
c
, -c
is illegal to be used as the second argument of an AddT
call (where T
denotes an unsigned integer type). We can used ^T(c-1)
as the second argument instead.
^T(v-1)
trick also works for an unsigned variable v
, but ^T(v-1)
is less efficient than T(-v)
.
^T(c-1)
, if c
is a typed value and its type is exactly T
, then the form can shortened as ^(c-1)
.
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
)
show := fmt.Println
atomic.AddUint64(&n, -m)
show(n) // 96 (97 - 1)
atomic.AddUint64(&n, -uint64(k))
show(n) // 94 (96 - 2)
atomic.AddUint64(&n, ^uint64(a - 1))
show(n) // 91 (94 - 3)
atomic.AddUint64(&n, ^(b - 1))
show(n) // 87 (91 - 4)
atomic.AddUint64(&n, ^uint64(c - 1))
show(n) // 82 (87 - 5)
atomic.AddUint64(&n, ^uint64(d - 1))
show(n) // 76 (82 - 6)
x := b; atomic.AddUint64(&n, -x)
show(n) // 72 (76 - 4)
atomic.AddUint64(&n, ^(m - 1))
show(n) // 71 (72 - 1)
atomic.AddUint64(&n, ^uint64(k - 1))
show(n) // 69 (71 - 2)
}
SwapT
function call is like a StoreT
function call, but returns the old value.
CompareAndSwapT
function call only applies the store operation when the current value matches the passed old value. The bool
return result of the CompareAndSwapT
function call indicates whether or not the store operation is applied.
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var n int64 = 123
var old = atomic.SwapInt64(&n, 789)
fmt.Println(n, old) // 789 123
swapped := atomic.CompareAndSwapInt64(&n, 123, 456)
fmt.Println(swapped) // false
fmt.Println(n) // 789
swapped = atomic.CompareAndSwapInt64(&n, 789, 456)
fmt.Println(swapped) // true
fmt.Println(n) // 456
}
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var n atomic.Int64
n.Store(123)
var old = n.Swap(789)
fmt.Println(n.Load(), old) // 789 123
swapped := n.CompareAndSwap(123, 456)
fmt.Println(swapped) // false
fmt.Println(n.Load()) // 789
swapped = n.CompareAndSwap(789, 456)
fmt.Println(swapped) // true
fmt.Println(n.Load()) // 456
}
sync/atomic
standard package to do atomic pointer operations, with the help of unsafe pointers.
unsafe.Pointer
, and vice versa. So values of *unsafe.Pointer
type can also be explicitly converted to unsafe.Pointer
, and vice versa.
T
can be an arbitrary type.
package main
import (
"fmt"
"sync/atomic"
"unsafe"
)
type T struct {x int}
func main() {
var pT *T
var unsafePPT = (*unsafe.Pointer)(unsafe.Pointer(&pT))
var ta, tb = T{1}, T{2}
// store
atomic.StorePointer(
unsafePPT, unsafe.Pointer(&ta))
fmt.Println(pT) // &{1}
// 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
fmt.Println(pT) // &{2}
// 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
}
unsafe
standard package.
Pointer
type and its methods to do atomic pointer operations, as the following code shows.
package main
import (
"fmt"
"sync/atomic"
)
type T struct {x int}
func main() {
var pT atomic.Pointer[T]
var ta, tb = T{1}, T{2}
// store
pT.Store(&ta)
fmt.Println(pT.Load()) // &{1}
// load
pa1 := pT.Load()
fmt.Println(pa1 == &ta) // true
// swap
pa2 := pT.Swap(&tb)
fmt.Println(pa2 == &ta) // true
fmt.Println(pT.Load()) // &{2}
// compare and swap
b := pT.CompareAndSwap(&ta, &tb)
fmt.Println(b) // false
b = pT.CompareAndSwap(&tb, &ta)
fmt.Println(b) // true
}
Pointer
type is protected by Go 1 compatibility guidelines.
Value
type provided in the sync/atomic
standard package can be used to atomically load and store values of any type.
*Value
has several methods: Load
, Store
, Swap
and CompareAndSwap
(The latter two are introduced in Go 1.17). The input parameter types of these methods are all interface{}
. So any value may be passed to the calls to these methods. But for an addressable Value
value v
, once the v.Store()
(a shorthand of (&v).Store()
) call has ever been called, then the subsequent method calls on value v
must also take argument values with the same concrete type as the argument of the first v.Store()
call, otherwise, panics will occur. A nil
interface argument will also make the v.Store()
call 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
}
package main
import (
"fmt"
"sync/atomic"
)
func main() {
type T struct {a, b, c int}
var x = T{1, 2, 3}
var y = T{4, 5, 6}
var z = T{7, 8, 9}
var v atomic.Value
v.Store(x)
fmt.Println(v) // {{1 2 3}}
old := v.Swap(y)
fmt.Println(v) // {{4 5 6}}
fmt.Println(old.(T)) // {1 2 3}
swapped := v.CompareAndSwap(x, z)
fmt.Println(swapped, v) // false {{4 5 6}}
swapped = v.CompareAndSwap(y, z)
fmt.Println(swapped, v) // true {{7 8 9}}
}
The Go 101 project is hosted on Github. Welcome to improve Go 101 articles by submitting corrections for all kinds of mistakes, such as typos, grammar errors, wording inaccuracies, description flaws, code bugs and broken links.
If you would like to learn some Go details and facts every serveral days, please follow Go 101's official Twitter account @zigo_101.
reflect
standard package.sync
standard package.sync/atomic
standard package.