single
и critical
принадлежат к двум совершенно разным классам OpenMP конструкций. single
представляет собой конструкцию для обрезки, наряду с for
и sections
. Конструкции Worksharing используются для распределения определенной работы между потоками. Такие конструкции являются «коллективными» в том смысле, что в правильных программах OpenMP все потоки должны сталкиваться с ними при выполнении и, кроме того, в том же порядке, включая также конструкции barrier
. Три распараллеливание конструкции охватывают три общих случая:
for
(а.к.а. конструкция цикла) распределяет автоматически итерации цикла между нитями - в большинстве случаев все нити получают работу, чтобы сделать;
sections
распределяет последовательность независимых блоков кода среди потоков - некоторые потоки выполняют работу. Это обобщение конструкции for
, поскольку цикл с 100 итерациями может быть выражен, например, 10 секций петель с 10 итерациями каждый.
single
выделяет блок кода для выполнения только одним потоком, часто первым, с которым он сталкивается (деталь реализации) - только один поток получает работу. single
в значительной степени эквивалентен sections
только с одной секцией.
Общей чертой всех распараллеливание конструкций является наличие неявного барьера на их конце, который барьер может быть выключен путем добавления оговорки к соответствующему OpenMP конструкции nowait
, но стандарт не требует таких поведение и с некоторыми временами работы OpenMP барьер может оставаться там, несмотря на наличие nowait
. Неправильно упорядоченные (т. Е. Вне последовательности в некоторых потоках) конструкторы совместной работы могут привести к взаимоблокировкам. Правильная программа OpenMP никогда не зациклится, когда будут присутствовать барьеры.
critical
- это схема синхронизации, рядом с master
, atomic
и другие. Конструкции синхронизации используются для предотвращения условий гонки и для приведения порядка в исполнение вещей.
critical
предотвращает условия гонки, предотвращая одновременное выполнение кода между потоками в так называемой конкурирующей группы. Это означает все темы от все параллельные области, сталкивающиеся с аналогичными именованными критическими конструктами, сериализуются;
atomic
превращает некоторые простые операции памяти в атомные, обычно используя специальные инструкции по сборке. Atomics завершается сразу как единый нерушимый блок. Например, атом, считываемый из некоторого местоположения одним потоком, который одновременно происходит с атомарной записью в одно и то же место другим потоком, либо вернет старое значение, либо обновленное значение, но никогда не будет промежуточным месивом бит из старого и нового значений;
master
выделяет блок кода для выполнения главным потоком (поток с идентификатором 0). В отличие от single
, в конце конструкции нет неявного барьера, и также нет требования, чтобы все потоки столкнулись с конструкцией master
. Кроме того, отсутствие скрытого барьера означает, что master
не очищает вид разделяемой памяти потоков (это важная, но очень слабо понятная часть OpenMP). master
- это в основном сокращенная версия для if (omp_get_thread_num() == 0) { ... }
.
critical
очень многогранная конструкция, как она способна сериализация различных частей коды в самом разных частях программного кода, даже в разных параллельных регионах (значащих в случае только вложенного параллелизм). Каждая конструкция critical
имеет необязательное имя, указанное в скобках сразу после. Анонимные критические конструкции имеют одно и то же имя для конкретной реализации. Когда поток входит в такую конструкцию, любой другой поток, сталкиваясь с другой конструкцией с тем же именем, приостанавливается до тех пор, пока исходный поток не выйдет из его конструкции. Затем процесс сериализации продолжается с остальными потоками.
Ниже приведено описание вышеизложенных концепций. Следующий код:
#pragma omp parallel num_threads(3)
{
foo();
bar();
...
}
приводит что-то вроде:
thread 0: -----< foo() >< bar() >-------------->
thread 1: ---< foo() >< bar() >---------------->
thread 2: -------------< foo() >< bar() >------>
(резьба 2 намеренно опоздавший)
Имея foo();
вызов в течение single
конструкции:
#pragma omp parallel num_threads(3)
{
#pragma omp single
foo();
bar();
...
}
приводит к чему-то вроде:
thread 0: ------[-------|]< bar() >----->
thread 1: ---[< foo() >-|]< bar() >----->
thread 2: -------------[|]< bar() >----->
Здесь [ ... ]
обозначает область применения single
конструкции и |
является неявным барьером на его конце. Обратите внимание, как поток latecomer 2 заставляет все остальные потоки ждать. В потоке 1 выполняется вызов foo()
в качестве примера. Рабочая среда OpenMP решает назначить задание первому потоку, чтобы встретить конструкцию.
Добавление пункта nowait
может удалить неявный барьер, в результате чего-то вроде:
thread 0: ------[]< bar() >----------->
thread 1: ---[< foo() >]< bar() >----->
thread 2: -------------[]< bar() >---->
Имея foo();
вызов в течение анонимного critical
конструкции:
#pragma omp parallel num_threads(3)
{
#pragma omp critical
foo();
bar();
...
}
результатов в чем-то вроде:
thread 0: ------xxxxxxxx[< foo() >]< bar() >-------------->
thread 1: ---[< foo() >]< bar() >------------------------->
thread 2: -------------xxxxxxxxxxxx[< foo() >]< bar() >--->
С xxxxx...
показано время, в течение которого поток ожидает, пока другие потоки выполнят критический конструкт с тем же именем, прежде чем он сможет ввести свою собственную конструкцию.
Критические конструкции разных имен не синхронизируются друг с другом. Например .:
#pragma omp parallel num_threads(3)
{
if (omp_get_thread_num() > 1) {
#pragma omp critical(foo2)
foo();
}
else {
#pragma omp critical(foo01)
foo();
}
bar();
...
}
приводит что-то вроде:
thread 0: ------xxxxxxxx[< foo() >]< bar() >---->
thread 1: ---[< foo() >]< bar() >--------------->
thread 2: -------------[< foo() >]< bar() >----->
Теперь поток 2 не синхронизируется с другими потоками, поскольку его критическая конструкция называется по-разному, и, следовательно, делает потенциально опасный одновременный вызов в foo()
.
С другой стороны, анонимные критические конструкции (и в общих конструкциях с тем же именем) синхронизировать друг с другом независимо от того, где в коде они:
#pragma omp parallel num_threads(3)
{
#pragma omp critical
foo();
...
#pragma omp critical
bar();
...
}
и в результате выполнения хронологию:
thread 0: ------xxxxxxxx[< foo() >]<...>xxxxxxxxxxxxxxx[< bar() >]------------>
thread 1: ---[< foo() >]<...>xxxxxxxxxxxxxxx[< bar() >]----------------------->
thread 2: -------------xxxxxxxxxxxx[< foo() >]<...>xxxxxxxxxxxxxxx[< bar() >]->
Большое спасибо. Я понял! Итак, можем ли мы сказать: критический - это своего рода щит, чтобы избежать состояния гонки, но единый для легких работ, которые нужно делать один раз? Если мы ставим критический параметр в if-statement, который позволяет просто вставить один конкретный поток, то он может работать аналогично одиночному? – Amir
Да, критически важно избегать условий гонки. Его типичное использование, если для замены предложения 'reduce()', когда переменная для уменьшения является массивом, как в C и C++, предложения 'reduce' применимы только к скалярным переменным. Что касается размещения критического значения внутри оператора if, как вы описываете, я действительно не вижу смысла ... – Gilles
Я думаю, что OP просто хотел узнать, эквивалентен ли он (если для одного потока с критическим и единственным) , Многие из предложений OpenMP можно эмулировать с помощью других предложений. Я думаю, было бы интересно увидеть минимальный набор предложений для подражания всем или большинству предложений. Иногда бывает полезно знать такие вещи. Бывают случаи, когда предложения OpenMP являются слишком ограничительными, и полезно знать, как это сделать (без использования другого интерфейса потоков, такого как pthreads). Самый распространенный пример, который я могу придумать, - это сделать индивидуальное сокращение. –