Вопрос

На большинстве распространенных платформ (наиболее важной из которых является 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. Реализация atomic ops, которую я использую, содержит x86 LOCK инструкции по каждой операции , если это поможет.
  2. Предполагать stuffDoneFlag каким-то образом должным образом очищается.Как - это не важно.
  3. Это очень упрощенный пример.Я создал его таким образом, чтобы вам не нужно было понимать весь контекст проблемы, чтобы ответить на нее.Я знаю, что это неэффективно.
Это было полезно?

Решение

Если ваш реальный 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; Это не правда!

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

<Ол>
  • Thread2 проверяет stuffDoneFlag, если 1 начинает читать someVariable.
  • Перед тем, как Thread2 завершит чтение, планировщик задач прервет свою задачу и приостановит ее на некоторое время.
  • Thread1 снова получит доступ к someVariable и изменит содержимое памяти.
  • Планировщик задач снова включает Thread2, и он продолжает работу, но содержимое памяти someVariable изменяется!
  • Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top