Распространение атомной операции / видимость (атомная нагрузка против атомной нагрузки RMW) - PullRequest
3 голосов
/ 09 марта 2019

Context & EMSP;

Я пишу поточно-безопасную библиотеку прототредов / сопрограмм на C ++, и я использую атомарность, чтобы сделать переключение задач без блокировки. Я хочу, чтобы это было максимально эффективно. У меня есть общее представление об элементарном программировании и программировании без блокировок, но у меня недостаточно опыта для оптимизации моего кода. Я провел много исследований, но было трудно найти ответы на мою конкретную проблему: Какова задержка распространения / видимость для различных атомарных операций при разных порядках памяти?

Текущие предположения & 101;

Я прочитал, что изменения в памяти распространяются из других потоков таким образом, что они могут стать видимыми:

  1. в разных порядках разным наблюдателям,
  2. с некоторой задержкой.

Я не уверен относительно того, применима ли эта отложенная видимость и непоследовательное распространение только к неатомарным чтениям, или также к атомарным чтениям, потенциально в зависимости от того, какой порядок памяти используется. Поскольку я работаю на компьютере с архитектурой x86, у меня нет возможности проверить поведение в слабо упорядоченных системах.

Все ли атомарные чтения всегда читают последние значения, независимо от типа операции и используемого порядка памяти? & emsp;

Я почти уверен, что все операции чтения-изменения-записи (RMW) всегда читают самое последнее значение, записанное любым потоком, независимо от используемого порядка памяти. То же самое можно сказать и о последовательно согласованных операциях, но только если все другие модификации переменной также последовательно согласованно . Обе говорят, что они медленные, что плохо для моей задачи. Если не все атомарные чтения получают самое последнее значение, то мне придется использовать операции RMW только для чтения последнего значения атомарной переменной или использовать атомарные чтения в цикле while, насколько я понимаю.

Зависит ли распространение записей (без учета побочных эффектов) от порядка памяти и используемой атомарной операции? & emsp;

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

Ответы [ 3 ]

4 голосов
/ 09 марта 2019

Атомная блокировка свободна?

Прежде всего, давайте избавимся от слона в комнате: использование 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), чтобы упорядочить атомарные операциипривести к предсказуемому результату.

1 голос
/ 09 марта 2019

Многопоточность - удивительная область.Во-первых, атомарное чтение не упорядочено после записи.Чтение значения не означает, что оно было написано ранее.Иногда такое чтение может когда-либо видеть (косвенно, другим потоком) результат некоторой последующей атомарной записи тем же потоком.

Последовательная согласованность явно касается видимости и распространения.Когда поток пишет атомарный «последовательно согласованный», он делает все свои предыдущие записи видимыми для других потоков (распространение).В таком случае (последовательно согласованное) чтение упорядочено по отношению к записи.

Как правило, наиболее эффективные операции - это «ослабленные» атомарные операции, но они обеспечивают минимальные гарантии при упорядочении.В принципе всегда есть некоторые парадоксы причинности ...: -)

0 голосов
/ 10 марта 2019

После некоторого обсуждения, вот мои выводы: во-первых, давайте определим, что означает последнее значение атомной переменной : во время настенного времени самая последняя запись в атомарную переменную, то есть из внешнейточка зрения наблюдателя.Если имеется несколько одновременных последних записей (т.е. на несколько ядер в течение одного и того же цикла), тогда не имеет значения, какое из них выбрано.

  1. Атомные загрузки любой памятиВ заказе нет гарантии, что значение latest прочитано.Это означает, что записи должны распространяться, прежде чем вы сможете получить к ним доступ.Это распространение может быть не в порядке относительно порядка, в котором они были выполнены, а также отличаться по порядку относительно разных наблюдателей.

    Это создает эффект относительность (как вФизика Эйнштейна), что у каждого потока есть своя «истина», и именно поэтому нам нужно использовать последовательную согласованность (или приобретать / освобождать) для восстановления причинности: если мы просто используем расслабленные нагрузки, то у нас может даже быть нарушенная причинность и кажущаясявременные петли, которые могут произойти из-за переупорядочения команд в сочетании с неупорядоченным распространением.Упорядочение памяти обеспечит, чтобы эти отдельные реальности, воспринимаемые отдельными потоками, были, по крайней мере, причинно согласованы.

  2. Атомарные операции чтения-изменения-записи (RMW) (такие как exchange, compare_exchange, fetch_add,…) гарантированно работают на самом последнем значении, как определено выше.Это означает, что распространение записей является принудительным и приводит к одному универсальному представлению в памяти (если все операции чтения выполняются из атомарных переменных с использованием операций RMW), независимо от потоков.Итак, если вы используете atomic.compare_exchange_strong(value,value, std::memory_order_relaxed) или atomic.fetch_or(0, std::memory_order_relaxed), то вы гарантированно воспримете один глобальный порядок модификации, который охватывает все атомарные переменные. Обратите внимание, что это не гарантирует вам порядок или причинность чтения не RMW.

Теперь, когда использовать, какой тип чтения?

Если вам нужна только причинность в каждом потоке (могут существовать разные взгляды на то, что произошло, в каком порядке, но, по крайней мере, каждый читатель имеет причинно-последовательный взгляд на мир), тогда атомные нагрузки и приобретают/ release или последовательной согласованности достаточно.

Но если вам также нужны свежие чтения (так что вы никогда не должны читать значения, отличные от последнего значения глобально (во всех потоках)), тогда вы должны использовать операции RMW для чтения.Они одни не создают причинно-следственную связь для неатомарных и не RMW-чтений, но все RMW-чтения во всех потоках имеют одно и то же представление о мире, которое всегда актуально.

Итак,в заключение : используйте атомные нагрузки, если разрешены разные мировоззрения, но если вам нужна объективная реальность , используйте RMW для загрузки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...