использование golang atomic LoadInt32/StoreInt32 (64)

Может ли кто-нибудь показать пример, где необходимо использование таких атомарных операций. не понимаю разницы между

import "sync/atomic"

...
var sharedA int64
var sharedB *int64
...
// concurent code
tmpVarA := sharedA
tmpVarB := *sharedB
// and
tmpVarA := atomic.LoadInt64(&sharedA)
tmpVarB := atomic.LoadInt64(sharedB)

person Oleg    schedule 05.11.2015    source источник
comment
Эмпирическое правило: никогда не используйте ничего из sync/atomic. Никогда. Но вы спросили: sync/atomic позволяет читать, например. int64 без гонок. Если вы смешиваете чтение и запись без синхронизации через каналы или что-то из синхронизации пакетов), ваш код является колоритным, что означает, что он имеет неопределенное поведение. Теперь иногда проще принудительно прочитать int64 без гонок с помощью atomic.LoadInt64, чем явную синхронизацию. Но как сказано: никогда не делай этого.   -  person Volker    schedule 05.11.2015


Ответы (1)


Это вообще не задокументировано в пакете, но обычно атомарные загрузки и сохранения нормальных значений существуют не для атомарности, потому что операции ЦП уже атомарны, а для упорядочения. Спецификация языка или документация по инструкциям ЦП дает вам определенные гарантии того, в каком порядке один ЦП будет видеть другой ЦП, если вы используете атомарные операции. Большинство современных процессоров не имеют таких гарантий, если вы не используете для них правильные (дорогие) инструкции.

Итак, в вашем примере (я предполагаю, поскольку пакет не задокументирован), если общие переменные были записаны горутиной сначала в sharedA, затем sharedB, при чтении затем без атомарных операций вы можете увидеть измененное значение sharedB и все еще старое значение sharedA. В разных семействах ЦП по-разному, если для сохранения или загрузки требуется дополнительная магия, чтобы правильно упорядочить, поэтому обычно языки заставляют вас использовать атомарные функции как для сохранения, так и для загрузки, а затем компилятор/библиотека знает, что нужно вашему фактическому ЦП.

Конечно, пакет ничего из этого не документирует, так что на практике нет никакой разницы, потому что мы не знаем, что на самом деле гарантирует пакет. Так что для всех практических целей это бесполезно.

person Art    schedule 05.11.2015
comment
но зачем нам операция загрузки? AddInt32 достаточно, не так ли? - person Jiang YD; 05.11.2015
comment
Я мог представить, что на 32-битной машине 64-битная загрузка/запись не является атомарной. Вы не хотите читать немодифицированные младшие 32 бита и модифицированные старшие 32 бита. - person mrd0ll4r; 05.11.2015
comment
Вы уверены, что нормальная загрузка и хранение int64 являются атомарными на всех поддерживаемых платформах? Даже на 32-битных платформах? Также: в модели памяти не упоминается, что операции в syn/atomic устанавливают отношение HB. - person Volker; 05.11.2015
comment
самое интересное, что детектор гонок находит и сообщает об этом случае как racecondition gorotine1:...Shared2 = 12345;..gorotine2.. readshared:= Shared2, но если их обернуть sync.Lock - он думает, что теперь все в порядке. - person Oleg; 05.11.2015
comment
@JiangYD В некоторых архитектурах (и я почти уверен, что современная x86 является одной из них) синхронизацию лучше всего выполнять до загрузки, а не после сохранения. - person Art; 05.11.2015
comment
@OlegGolovanov, потому что sync.Lock имеет встроенные гарантии упорядочения, которые, как я теперь вижу, также не задокументированы. Но если мы предположим, что он ведет себя как все остальные, любые записи, сделанные до Unlock в одной горутине, будут видны другим горутинам после их вызова Lock. К сожалению, модель памяти Go не очень хорошо документирована, поэтому я могу делать только разумные предположения о том, как ведут себя процессоры и другие языки. - person Art; 05.11.2015
comment
@Art синхронизация и гарантии HB синхронизации пакетов задокументированы в модели Go Memory, см., например. golang.org/ref/mem#tmp_8 . - person Volker; 05.11.2015