1

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

Одно из наиболее заслуживающих доверия обсуждение этой темы - this article by Arch Robison. Примером, который он использует, является задача передачи значения из одного потока в другой:

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

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

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

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

Мой вопрос, как сделать следующее в C

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

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

ответ

0

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

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

+0

В ответ на последнее предложение в вашем первом параграфе Энди Робинсон говорит, что обе переменные должны быть volatile для * обеих сторон, так как volatile также предотвратит переупорядочение присвоений флагу и матрице. Но я предполагаю, что вы оставили эту проблему переупорядочения для простоты. – user7610

+0

Матрица, особенно в приложении multiThreaded, где предполагается, что она будет передаваться между потоками, вероятно, будет распределена динамически и поэтому доступна через свой указатель. Указатели обычно имеют размер регистра :( –

+0

Вам нужна изменчивость только на стороне отправителя, если отправитель повторно использует матрицу или указатель. Переупорядочение является самостоятельным вопросом. В качестве статута Necrolis в C11 вы можете использовать _Atomic. – Matthias

1

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

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

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

0

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

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