Видимость атомарных инструкций и обновления переменных
-
06-07-2019 - |
Вопрос
На большинстве распространенных платформ (наиболее важной из которых является x86;Я понимаю, что некоторые платформы имеют чрезвычайно сложные модели памяти, которые почти не дают гарантий, полезных для многопоточности, но меня не волнуют редкие контрпримеры), безопасен ли следующий код?
Резьба 1:
someVariable = doStuff();
atomicSet(stuffDoneFlag, 1);
Поток 2:
while(!atomicRead(stuffDoneFlag)) {} // Wait for stuffDoneFlag to be set.
doMoreStuff(someVariable);
Предполагая стандартные, разумные реализации атомарных операций:
- Является ли назначение потока 1
someVariable
гарантированно завершится доatomicSet()
называется? - Гарантируется ли, что поток 2 увидит назначение
someVariable
перед звонкомdoMoreStuff()
при условии, что он читаетstuffDoneFlag
атомарно?
Правки:
- Реализация atomic ops, которую я использую, содержит x86
LOCK
инструкции по каждой операции , если это поможет. - Предполагать
stuffDoneFlag
каким-то образом должным образом очищается.Как - это не важно. - Это очень упрощенный пример.Я создал его таким образом, чтобы вам не нужно было понимать весь контекст проблемы, чтобы ответить на нее.Я знаю, что это неэффективно.
Решение
Если ваш реальный x86-код имеет сохранение для someVariable до сохранения в atomicSet в потоке 1 и загрузку someVariable после загрузки в atomicRead в потоке 2, то у вас все будет хорошо. Руководство разработчика программного обеспечения Intel, том 3A , определяет модель памяти для x86 в разделе 8.2, и здесь должно быть достаточно ограничений внутри-потокового хранилища-хранилища и нагрузки-загрузки.
Однако, возможно, что-то не мешает вашему компилятору переупорядочить инструкции, сгенерированные из любого языка более высокого уровня, который вы используете для атомарных операций. Р>
Другие советы
1)Да
2)Да
И то, и другое работает.
Этот код выглядит поточно-ориентированным, но я подвергаю сомнению эффективность вашего спинлока (пока цикл), если вы не вращаетесь только в течение очень короткого промежутка времени. Ни одна из систем не гарантирует, что поток 2 не будет полностью загружать все время обработки. Р>
Я бы порекомендовал использовать некоторые фактические примитивы синхронизации (выглядит как boost :: condition_variable - это то, что вам нужно) вместо того, чтобы полагаться на спин-блокировку.
Атомарные инструкции гарантируют, что поток 2 ожидает, пока поток 1 завершит установку переменной, прежде чем поток 2 продолжит работу. Однако есть два ключевых вопроса:
1) someVariable
должен быть объявлен как volatile ', чтобы компилятор не оптимизировал его распределение, например сохранить его в регистре или отложить запись.
2) второй поток блокируется во время ожидания сигнала (обозначается как спинлок) ). Ваша платформа, вероятно, предоставляет гораздо лучшие блоки и механизмы блокировки и сигнализации, но относительно простым улучшением было бы просто sleep ()
в теле while ()
потока 2. . р>
dsimcha писал: «Предположим, что stuffDoneFlag как-то правильно очищен. Как не важно. & Quot; Это не правда!
Давайте посмотрим сценарий:
<Ол>