Атомарные функции выполняют задачу изолированным образом, когда кажется, что все части задачи выполняются мгновенно или не выполняются вообще.
В этом случае LoadInt32 и StoreInt32 гарантируют, что целое число сохраняется и извлекается таким образом, что при загрузке не будет получено частичное сохранение. Однако вам нужно, чтобы обе стороны использовали атомарные функции, чтобы это работало правильно. Пример raft неверен как минимум по двум причинам.
Две атомарные функции не действуют как одна атомарная функция, поэтому чтение старой и установка новой в две строки — это состояние гонки. Вы можете читать, затем кто-то другой устанавливает, затем вы устанавливаете, и вы возвращаете ложную информацию для предыдущего значения до того, как вы его установили.
Не все, обращающиеся к MinimumElectionTimeoutMS, используют атомарные операции. Это означает, что использование атомарных чисел в этой функции практически бесполезно.
Как это исправить?
func resetElectionTimeoutMS(newMin, newMax int) (int, int) {
oldMin := atomic.SwapInt32(&MinimumElectionTimeoutMS, int32(newMin))
oldMax := atomic.SwapInt32(&maximumElectionTimeoutMS, int32(newMax))
return int(oldMin), int(oldMax)
}
Это гарантирует, что oldMin будет минимальным, существовавшим до свопа. Однако вся функция по-прежнему не является атомарной, поскольку конечным результатом может быть пара oldMin и oldMax, которая никогда не вызывалась с помощью resetElectionTimeoutMS. Для этого... просто используйте замки.
Каждая функция также должна быть изменена для выполнения атомарной загрузки:
func minimumElectionTimeout() time.Duration {
min := atomic.LoadInt32(&MinimumElectionTimeoutMS)
return time.Duration(min) * time.Millisecond
}
Я рекомендую вам внимательно рассмотреть цитату VonC, упомянутую в документации golang atomic:
Эти функции требуют большой осторожности для правильного использования. За исключением специальных низкоуровневых приложений, синхронизацию лучше производить с помощью каналов или средств пакета синхронизации.
Если вы хотите понять атомарные операции, я рекомендую вам начать с http://preshing.com/20130618/atomic-vs-non-atomic-operations/. Это касается операций загрузки и сохранения, используемых в вашем примере. Однако есть и другие применения для атомов. В обзоре пакета atomic рассматриваются некоторые интересные вещи, такие как атомарная подкачка (пример, который я привел), сравните и своп (известный как CAS), и Добавление.
Забавная цитата из ссылки, которую я вам дал:
хорошо известно, что в x86 32-битная инструкция mov является атомарной, если операнд в памяти выровнен естественным образом, и неатомарной в противном случае. Другими словами, атомарность гарантируется только в том случае, если 32-битное целое число расположено по адресу, который точно кратен 4.
Другими словами, в обычных сегодняшних системах атомарные функции, используемые в вашем примере, фактически не работают. Они уже атомные! (Однако они не гарантируются, если вам нужно, чтобы они были атомарными, лучше указать это явно)
person
Stephen Weinberg
schedule
27.08.2014