В вашем коде в настоящее время состояние гонки , поэтому результат неверен. Чтобы проиллюстрировать, почему это так, воспользуемся простым примером:
Вы работаете в 2 потоках, а массив - int input[4] = {1, 2, 3, 4};
. Вы правильно инициализируете sum
до 0
и готовы к запуску цикла. На первой итерации вашего цикла поток 0 и поток 1 читают sum
из памяти как 0
, а затем добавьте их соответствующий элемент в sum
и запишите его обратно в память. Однако это означает, что поток 0 пытается записать sum = 1
в память (первый элемент - 1
и sum = 0 + 1 = 1
), а поток 1 пытается записать sum = 2
в память (второй элемент - 2
и sum = 0 + 2 = 2
). Конечный результат этого кода зависит от того, какой из последних заканчивается последним, и поэтому записывает в память последним, что является условием гонки. Не только это, но и в этом конкретном случае ни один из ответов, которые мог бы произвести код, правильный! Есть несколько способов обойти это; Ниже я расскажу о трех основных:
#pragma omp critical
:
В OpenMP существует так называемая директива critical
. Это ограничивает код так, что только один поток может что-то делать за раз. Например, ваш for
-loop может быть записан:
#pragma omp parallel for schedule(static)
for(i = 0; i < snum; i++) {
int *tmpsum = input + i;
#pragma omp critical
sum += *tmpsum;
}
Это устраняет условие гонки, когда только один поток обращается и записывается в sum
за раз. Тем не менее, директива critical
очень плоха для производительности и, вероятно, убьет большую часть (если не все) выгоды, получаемой от использования OpenMP в первую очередь.
#pragma omp atomic
:
Директива atomic
очень похожа на директиву critical
. Основное отличие состоит в том, что, хотя директива critical
применяется ко всему, что вы хотели бы сделать по одному потоку за раз, директива atomic
применяется только к операциям чтения / записи памяти. Поскольку все, что мы делаем в этом примере кода, - это чтение и запись для суммирования, эта директива будет работать отлично:
#pragma omp parallel for schedule(static)
for(i = 0; i < snum; i++) {
int *tmpsum = input + i;
#pragma omp atomic
sum += *tmpsum;
}
Производительность atomic
обычно значительно лучше, чем у critical
. Тем не менее, это еще не лучший вариант в вашем конкретном случае.
reduction
:
Метод, который вы должны использовать, и метод, который уже был предложен другими, - это reduction
. Вы можете сделать это, изменив for
-loop на:
#pragma omp parallel for schedule(static) reduction(+:sum)
for(i = 0; i < snum; i++) {
int *tmpsum = input + i;
sum += *tmpsum;
}
Команда reduction
сообщает OpenMP, что, хотя цикл работает, вы хотите, чтобы каждый поток отслеживал свои собственные sum
и добавить их все в конце цикла. Это самый эффективный метод, так как весь цикл теперь выполняется параллельно, при этом единственные служебные данные находятся прямо в конце цикла, когда необходимо добавить значения sum
каждого из потоков.