Атомарные инструкции и переменное обновление видимости - PullRequest
1 голос
/ 23 октября 2009

На большинстве распространенных платформ (наиболее важными из которых являются x86; я понимаю, что на некоторых платформах очень сложные модели памяти, которые почти не дают гарантий, полезных для многопоточности, но мне не нужны редкие контрпримеры), следующий код безопасно?

Тема 1:

someVariable = doStuff();
atomicSet(stuffDoneFlag, 1);

Тема 2:

while(!atomicRead(stuffDoneFlag)) {}  // Wait for stuffDoneFlag to be set.
doMoreStuff(someVariable);

Принимая стандартные, разумные реализации атомарных операций:

  1. Гарантируется ли выполнение потока 1 для someVariable до вызова atomicSet()?
  2. Гарантируется ли в потоке 2 присваивание someVariable перед вызовом doMoreStuff() при условии, что он читает stuffDoneFlag атомарно?

Правки:

  1. Используемая мной реализация атомарных операций содержит инструкцию x86 LOCK в каждом операция, если это поможет.
  2. Предположим, stuffDoneFlag как-то правильно очищено. Как не важно.
  3. Это очень упрощенный пример. Я создал это таким образом, чтобы вам не пришлось понимать весь контекст проблемы, чтобы ответить на него. Я знаю, что это не эффективно.

Ответы [ 5 ]

2 голосов
/ 30 октября 2009

Если ваш реальный x86-код имеет сохранение для someVariable до сохранения в atomicSet в потоке 1 и загрузку someVariable после загрузки в atomicRead в потоке 2, то у вас все будет в порядке. Руководство разработчика программного обеспечения Intel Том 3A определяет модель памяти для x86 в Разделе 8.2, и здесь должно быть достаточно ограничений внутрипотокового хранилища и загрузки-загрузки.

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

0 голосов
/ 23 октября 2009

dsimcha написал: «Предположим, что stuffDoneFlag как-то правильно очищен. Как не важно». Это не правда!

Давайте посмотрим сценарий:

  1. Thread2 проверяет stuffDoneFlag, если 1 начинает читать someVariable.
  2. Перед тем, как Thread2 завершит чтение, планировщик задач прервет свою задачу и приостановит ее на некоторое время.
  3. Thread1 снова получит доступ к someVariable и изменит содержимое памяти.
  4. Планировщик задач снова включает Thread2, и он продолжает работу, но содержимое памяти someVariable изменяется!
0 голосов
/ 23 октября 2009

Этот код выглядит поточно-ориентированным, но я подвергаю сомнению эффективность вашего spinlock (цикла while), если только вы не вращаетесь только в течение очень короткого промежутка времени. Ни одна из систем не гарантирует, что поток 2 не будет полностью загружать все время обработки.

Я бы рекомендовал использовать некоторые фактические примитивы синхронизации (похоже, boost :: condition_variable - то, что вам нужно) вместо того, чтобы полагаться на спин-блокировку.

0 голосов
/ 23 октября 2009

атомарные инструкции гарантируют, что поток 2 ожидает, пока поток 1 завершит установку переменной, прежде чем поток 2 продолжит работу. Однако есть два ключевых вопроса:

1) someVariable должен быть объявлен как ' volatile ', чтобы компилятор не оптимизировал его распределение, например. сохранение его в регистре или отсрочка записи.

2) второй поток блокируется во время ожидания сигнала (обозначается spinlocking ). Ваша платформа, вероятно, предоставляет гораздо лучшие блоки и сигнальные примитивы и механизмы, но относительно простым улучшением было бы просто sleep() в теле while() потока 2.

0 голосов
/ 23 октября 2009

1) Да

2) Да

Обе работы.

...