Атомная блокировка свободна?
Прежде всего, давайте избавимся от слона в комнате: использование atomic
в вашем коде не гарантирует реализацию без блокировки.atomic
является только активатором для реализации без блокировки.is_lock_free()
скажет вам, действительно ли он свободен от блокировки для реализации C ++ и используемых вами базовых типов.
Какая последняя ценность?
Термин «последняя» очень неоднозначен в мире многопоточности.Поскольку то, что является «самым последним» для одного потока, которое может быть переведено в спящий режим ОС, может больше не быть тем, что является последним для другого активного потока. Только для
std::atomic
гарантирует защиту от гонок, гарантируя, что R, M и RMW , выполненные на одном атоме в одном потоке, выполняются атомарно, без прерывания, и что все другие потоки видят либо значение до, либо значениепосле, но никогда не то, что между.Поэтому atomic
синхронизирует потоки, создавая порядок между параллельными операциями над одним и тем же атомарным объектом.
Вы должны видеть каждый поток как параллельную вселенную со своим собственным временем, которое не знает о времени в параллельных вселенных.И, как и в квантовой физике, единственное, что вы можете знать в одном потоке о другом потоке, - это то, что вы можете наблюдать (то есть отношение «произошло до» между вселенными).
Это означает, что вы не должны понимать многопоточное время, как если бы оно было абсолютным «последним» во всех потоках.Вы должны понимать время относительно других потоков.Вот почему атомы не создают абсолютную последнюю версию, а только обеспечивают последовательное упорядочение последовательных состояний, которые будут иметь атомы.
Распространение
Распространение не зависит ни от порядка памяти, ни от выполняемой атомарной операции. memory_order о последовательных ограничениях на неатомарные переменные вокруг атомарных операций, которые выглядят как заборы.Лучшим объяснением того, как это работает, безусловно, является презентация Херба Саттерса , которая определенно стоит своих полутора часов, если вы работаете над оптимизацией многопоточности.
Хотя вполне возможно, что конкретная реализация C ++ могла бы реализовать некоторую элементарную операцию таким образом, чтобы влиять на распространение, вы не можете полагаться на любое такое наблюдение, которое бы вы сделали, так как не было бы никакой гарантии, что распространение работает вто же самое в следующем выпуске компилятора или на другом компиляторе на другой архитектуре процессора.
Но имеет ли значение распространение?
Когда разрабатывает алгоритмы без блокировки , заманчиво читать атомарные переменные, чтобы получить последний статус.Но в то время как такой доступ только для чтения является атомарным, действие сразу после этого не является.Таким образом, следующие инструкции могут предполагать состояние, которое уже устарело (например, потому что поток отправляется в спящий режим сразу после атомарного чтения).
Возьмите if(my_atomic_variable<10)
и предположите, что вы прочитали 9. Предположим, вы находитесь в лучшем мире, и 9 будет абсолютно последним значением, установленным всеми параллельными потоками.Сравнение его значения с <10
не является атомарным, поэтому, когда сравнение успешно и ветки if
, my_atomic_variable
могут уже иметь новое значение 10. И такого рода проблемы могут возникать независимо от того, как быстро распространяется,и даже если чтение будет гарантированно всегда получать последнее значение.И я даже еще не упомянул проблему ABA .
Единственное преимущество чтения состоит в том, чтобы избежать гонки данных и UB.Но если вы хотите синхронизировать решения / действия между потоками, вам нужно использовать RMW, например сравнивать и заменять (например, atomic_compare_exchange_strong
), чтобы упорядочить атомарные операциипривести к предсказуемому результату.