Я должен распараллелить следующий код с двумя блокировками, и вывод должен быть в порядке:Hello
World
Bye
Но когда я запускаю потоки, они случайным образом выполняют свою работу.
#include <omp.h>
#include <stdio.h>
int main()
{
int p;
omp_lock_t lock;
omp_init_lock(&lock);
#pragma omp parallel sections default(shared)
{
#pragma omp section
{
p = omp_get_thread_num();
omp_set_lock(&lock);
printf("Th%d: Hello\n",p);
omp_unset_lock(&lock);
}
#pragma omp section
{
p = omp_get_thread_num();
omp_set_lock(&lock);
printf("Th%d: World\n",p);
omp_unset_lock(&lock);
}
#pragma omp section
{
p = omp_get_thread_num();
printf("Th%d: Bye\n",p);
}
}
omp_destroy_lock(&lock);
return 0;
}
Ваша блокировка не гарантирует порядок ваших тем — только атомарный доступ к stdout
. (например) Итак, для последнего вы всегда будете получать вывод «целая строка» Hello\n World\n Bye\n
, а не HeWoByllo\ne\nrld\n
. Но при этом вы можете получить: World\n Bye\n Hello\n
. Кроме того, означает ли default(shared)
, что p
используется совместно? Я думаю, что p
должно быть private
[или должно быть установлено внутри критической секции]. Кроме того, 3-й раздел не выполняет никакой блокировки. Я только что запустил программу и получил Th7
во всех сообщениях, так что [AFAICT], p
должен быть приватным
Из моих топовых комментариев:
Ваша блокировка не гарантирует порядок ваших тем — только атомарный доступ к stdout
.
Итак, для последнего (например) вы всегда будете получать вывод "целая строка":
Hello\n
World\n
Bye\n
И не:
HeWoByllo\ne\nrld\n
Но при этом вы можете получить:
World\n
Bye\n
Hello\n
Кроме того, означает ли default(shared)
, что p
используется совместно? Я думаю, что p
должно быть private
[или должно быть установлено внутри критической секции].
Кроме того, 3-й раздел не выполняет никакой блокировки.
Я только что запустил программу и получил Th7
во всех сообщениях, поэтому [AFAICT], p
должен быть приватным.
Один из способов решить эту проблему — использовать «блокировку билетов» [*].
Обновлено: после повторного прочтения вашего вопроса акцент на двух замках может указывать на альтернативное решение, которое я добавил в ОБНОВЛЕНИИ ниже.
Поскольку мы не можем предсказать, каким будет p
, каждому разделу нужен порядковый номер.
Например, Hello
нужно 1, World
нужно 2, а Bye
нужно 3.
Нам нужно добавить общую переменную, которая указывает, какой раздел может быть следующим. Каждый раздел должен зацикливаться на этой переменной [под замком] до тех пор, пока она не совпадет с назначенным ему порядковым/порядковым номером, и увеличивать его на выходе, чтобы разрешить выполнение потока, следующего в последовательности.
[*] «Билетный замок» создан по образцу (например) пекарни, где вы «берете номер» при входе и покупаете продукты, когда на вывеске написано: «Сейчас подается ...» См.: https://en. wikipedia.org/wiki/Билет_блокировка
В общем, одна из приятных особенностей блокировки билетов заключается в том, что она гарантирует прогресс и сбалансированный доступ. На практике количество повторных попыток относительно невелико/нечасто. Это особенно верно, если он реализован с использованием примитивов stdatomic.h
.
Во всяком случае, вот что я придумал:
#include <omp.h>
#include <stdio.h>
int main()
{
int p;
int owner = 1;
int more = 1;
omp_lock_t lock;
omp_init_lock(&lock);
#pragma omp parallel sections default(shared) private(p) private(more)
{
#pragma omp section
{
more = 1;
while (more) {
p = omp_get_thread_num();
omp_set_lock(&lock);
if (owner == 1) {
printf("Th%d: Hello\n",p);
more = 0;
owner += 1;
}
omp_unset_lock(&lock);
}
}
#pragma omp section
{
more = 1;
while (more) {
p = omp_get_thread_num();
omp_set_lock(&lock);
if (owner == 2) {
printf("Th%d: World\n",p);
more = 0;
owner += 1;
}
omp_unset_lock(&lock);
}
}
#pragma omp section
{
more = 1;
while (more) {
p = omp_get_thread_num();
omp_set_lock(&lock);
if (owner == 3) {
printf("Th%d: Bye\n",p);
more = 0;
owner += 1;
}
omp_unset_lock(&lock);
}
}
}
omp_destroy_lock(&lock);
return 0;
}
Вот вывод программы. Значения p
могут варьироваться от запуска к запуску, но теперь порядок всегда одинаков:
Th1: Hello
Th7: World
Th4: Bye
ОБНОВЛЯТЬ:
Приведенное выше решение является общим [и я использовал его несколько раз в реальном производственном коде], но оно может не соответствовать требованию использования двух блокировок.
Что я могу придумать, так это иметь две блокировки, которые инициализируются и блокируются основным потоком перед входом в какие-либо критические разделы.
Первая секция (Hello
) не нуждается в блокировке и идет первой. Две другие секции (World
и Bye
) сначала зафиксируются на lock2
и lock3
соответственно.
Когда Hello
заканчивается, он разблокируется lock2
.
Это позволяет World
получить блокировку и запустить. По завершении разблокируются lock2
и lock3
.
Разблокировка lock3
позволяет Bye
получить этот замок. Он печатает, а затем разблокирует lock3
Вот что я придумал для этого:
#include <omp.h>
#include <stdio.h>
int main()
{
int p;
omp_lock_t lock2;
omp_init_lock(&lock2);
omp_set_lock(&lock2);
omp_lock_t lock3;
omp_init_lock(&lock3);
omp_set_lock(&lock3);
#pragma omp parallel sections default(shared) private(p)
{
#pragma omp section
{
p = omp_get_thread_num();
printf("Th%d: Hello\n",p);
omp_unset_lock(&lock2);
}
#pragma omp section
{
p = omp_get_thread_num();
omp_set_lock(&lock2);
printf("Th%d: World\n",p);
omp_unset_lock(&lock2);
omp_unset_lock(&lock3);
}
#pragma omp section
{
p = omp_get_thread_num();
omp_set_lock(&lock3);
printf("Th%d: Bye\n",p);
omp_unset_lock(&lock3);
}
}
omp_destroy_lock(&lock2);
omp_destroy_lock(&lock3);
return 0;
}
Да, я могу использовать только разделы, и я должен использовать ровно 2 замка.