Использование флага для связи между потоками - PullRequest
1 голос
/ 28 февраля 2012

В Интернете можно найти много споров об использовании ключевого слова volatile в параллельном программировании, иногда с противоречивой аргументацией.

Одним из наиболее заслуживающих доверия обсуждений этой темы является эта статья Арка Робисона .В качестве примера он использует задачу передачи значения из одного потока в другой:

Поток 1. вычисляет матричный продукт и передает его в Поток 2, который делает с ним что-то другое.Матрица является переменной M, а флаг является volatile указателем R.

  1. . Поток 1 умножает, вычисляет матричное произведение M и атомарно устанавливает R для указания на M.
  2. Поток 2 ожидает, пока R! = NULL, а затем использует M в качестве фактора для вычисления другого матричного произведения.

Другими словами, M - это сообщение, а R - флаг готовности.

Автор утверждает, что хотя объявление R в качестве энергозависимого решит проблему с распространением изменения из потока 1 в поток 2, оно не дает никаких гарантий относительно того, каким будет значение M, когда этослучается.И назначения на R и M могут быть переупорядочены.Поэтому нам нужно сделать оба параметра M и R volatile или использовать некоторый механизм синхронизации в некоторой библиотеке, такой как pthreads.

У меня вопрос, как сделать следующее в C

1)Как разделить один флаг между двумя потоками - Как атомарно назначить ему, убедитесь, что другой поток увидит изменение и проверит изменения в другом потоке.Законно ли использование volatile в этом случае?Или какая-нибудь библиотека может предложить концептуально лучший или более быстрый способ, возможно, с использованием барьеров памяти?

2) Как правильно сделать пример Робисона, как отправить матрицу M из одного потока в другой и сделать это безопасно(и предпочтительно переносимо с нитями)

Ответы [ 3 ]

1 голос
/ 28 февраля 2012

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

Самый простой способ сделать это - использовать CAS.большинство встроенных функций CAS обеспечивают полный барьер памяти на уровне компилятора и шины процессора.в MSVC вы можете использовать функции Interlock*, BTS, BTR, Inc, Dec, Exchange и Add все будут работать для флага, для GCC вы будете использовать варианты на основе __sync_*.

Для более портативных опций вы можете использовать pthread_mutex или pthread_cond.если вы можете использовать C11 , вы также можете посмотреть ключевое слово _Atomic.

0 голосов
/ 28 февраля 2012

Классический способ для потока 1 - поместить указатель на динамически распределенную матрицу в очередь производителя-потребителя, в которой ожидает поток 2.После нажатия поток 1 может выделить другой M и начать работать над ним, если он того пожелает.

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

0 голосов
/ 28 февраля 2012

«volatile» является подсказкой для компилятора не оптимизировать доступ к памяти, т. Е. Не предполагать, что значение в памяти не изменилось с момента последней (локальной) записи.Без этой подсказки компилятор может предположить, что значение регистра, из которого копируется переменная, все еще допустимо.Таким образом, хотя весьма маловероятно, что матрица хранится в регистре, в общем случае обе переменные должны быть энергозависимыми или, точнее, энергозависимыми для приемника.

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

...