В модели памяти Go ничего не говорится об атомистике и их связи с фехтованием памяти.
Хотя многие внутренние пакеты, похоже, полагаются на порядок памяти, который можно было бы обеспечить, если бы атомы создавали ограждения вокруг них. Подробности см. в этом выпуске .
После недопонимания того, как это действительно работает, я обратился к источникам, в частности src / runtime / internal / atomic / atomic_amd64.go и обнаружил следующие реализации Load
и Store
:
//go:nosplit
//go:noinline
func Load(ptr *uint32) uint32 {
return *ptr
}
Store
реализован в asm_amd64.s
в одном пакете.
TEXT runtime∕internal∕atomic·Store(SB), NOSPLIT, $0-12
MOVQ ptr+0(FP), BX
MOVL val+8(FP), AX
XCHGL AX, 0(BX)
RET
Оба выглядят так, как если быони не имели ничего общего с параллелизмом.
Я изучил другие архитектуры, но реализация кажется эквивалентной.
Однако, если атомы действительно слабые и не дают никаких гарантий упорядочения памяти, то приведенный ниже код может дать сбой, но это не так. ,
В качестве дополнения я попытался заменить атомарные вызовы простыми назначениями, но он все равно дает согласованный и «успешный» результат в обоих случаях.
func try() {
var a, b int32
go func() {
// atomic.StoreInt32(&a, 1)
// atomic.StoreInt32(&b, 1)
a = 1
b = 1
}()
for {
// if n := atomic.LoadInt32(&b); n == 1 {
if n := b; n == 1 {
if a != 1 {
panic("fail")
}
break
}
runtime.Gosched()
}
}
func main() {
n := 1000000000
for i := 0; i < n ; i++ {
try()
}
}
Следующая мысль состояла в том, что компилятор совершает магиюпредоставить гарантии заказа. Ниже приведен список вариантов с атомарными Store
и Load
без комментариев. Полный список доступен на pastebin .
// Anonymous function implementation with atomic calls inlined
TEXT %22%22.try.func1(SB) gofile../path/atomic.go
atomic.StoreInt32(&a, 1)
0x816 b801000000 MOVL $0x1, AX
0x81b 488b4c2408 MOVQ 0x8(SP), CX
0x820 8701 XCHGL AX, 0(CX)
atomic.StoreInt32(&b, 1)
0x822 b801000000 MOVL $0x1, AX
0x827 488b4c2410 MOVQ 0x10(SP), CX
0x82c 8701 XCHGL AX, 0(CX)
}()
0x82e c3 RET
// Important "cycle" part of try() function
0x6ca e800000000 CALL 0x6cf [1:5]R_CALL:runtime.newproc
for {
0x6cf eb12 JMP 0x6e3
runtime.Gosched()
0x6d1 90 NOPL
checkTimeouts()
0x6d2 90 NOPL
mcall(gosched_m)
0x6d3 488d0500000000 LEAQ 0(IP), AX [3:7]R_PCREL:runtime.gosched_m·f
0x6da 48890424 MOVQ AX, 0(SP)
0x6de e800000000 CALL 0x6e3 [1:5]R_CALL:runtime.mcall
if n := atomic.LoadInt32(&b); n == 1 {
0x6e3 488b442420 MOVQ 0x20(SP), AX
0x6e8 8b08 MOVL 0(AX), CX
0x6ea 83f901 CMPL $0x1, CX
0x6ed 75e2 JNE 0x6d1
if a != 1 {
0x6ef 488b442428 MOVQ 0x28(SP), AX
0x6f4 833801 CMPL $0x1, 0(AX)
0x6f7 750a JNE 0x703
0x6f9 488b6c2430 MOVQ 0x30(SP), BP
0x6fe 4883c438 ADDQ $0x38, SP
0x702 c3 RET
Как видите, заборы или замки снова не установлены.
Примечание: все тестывыполняются на x86_64 и i5-8259U
Вопрос:
Итак, есть ли смысл переносить простой разыменование указателя в вызове функции или есть какой-то скрытый смысли почему эти атомы все еще работают как барьеры памяти? (если они делают)