#c #locking #openmp
#c #запирание #openmp
Вопрос:
Я должен распараллелить следующий код с 2 блокировками, и вывод должен быть в порядке:
Hello
World
Bye
Но когда я запускаю потоки, они случайным образом выполняют работу.
#include <omp.h>
#include <stdio.h>
int main()
{
int p;
omp_lock_t lock;
omp_init_lock(amp;lock);
#pragma omp parallel sections default(shared)
{
#pragma omp section
{
p = omp_get_thread_num();
omp_set_lock(amp;lock);
printf("Th%d: Hellon",p);
omp_unset_lock(amp;lock);
}
#pragma omp section
{
p = omp_get_thread_num();
omp_set_lock(amp;lock);
printf("Th%d: Worldn",p);
omp_unset_lock(amp;lock);
}
#pragma omp section
{
p = omp_get_thread_num();
printf("Th%d: Byen",p);
}
}
omp_destroy_lock(amp;lock);
return 0;
}
Комментарии:
1. Да, я могу использовать только разделы, и я должен использовать ровно 2 блокировки.
2.Ваша блокировка не гарантирует упорядочение ваших потоков — только атомарный доступ
stdout
. (например) Итак, для последнего вы всегда будете получать вывод «целой строки»Hellon Worldn Byen
, а неHeWoByllonenrldn
. Но с этим вы можете получить :Worldn Byen Hellon
. Кроме того,default(shared)
означает ли это, чтоp
он является общим? Я думаюp
, что это должно бытьprivate
[или должно быть установлено внутри критической секции]. Кроме того, 3-й раздел не выполняет никакой блокировки. Я только что запустил программу и получилTh7
все сообщения, поэтому [AFAICT]p
должен быть закрытым
Ответ №1:
Из моих лучших комментариев:
Ваша блокировка не гарантирует упорядочение ваших потоков — только атомарный доступ stdout
.
Итак, для последнего (например) вы всегда будете получать вывод «целой строки»:
Hellon
Worldn
Byen
И, не:
HeWoByllonenrldn
Но с этим вы можете получить:
Worldn
Byen
Hellon
Кроме того, default(shared)
означает ли это, что p
он является общим? Я думаю p
, что это должно быть private
[или должно быть установлено внутри критической секции].
Кроме того, 3-й раздел не выполняет никакой блокировки.
Я только что запустил программу и получил Th7
все сообщения, поэтому [AFAICT] p
должен быть приватным.
Один из способов решить эту проблему — использовать «блокировку билета» [*].
Редактировать: после повторного прочтения вашего вопроса акцент на двух блокировках может указывать на альтернативное решение, которое я добавил в ОБНОВЛЕНИИ ниже.
Поскольку мы не можем предсказать, что p
будет, каждому разделу нужен порядковый номер.
Например, Hello
требуется 1, World
требуется 2 и Bye
требуется 3.
Нам нужно добавить общую переменную, которая указывает, какой раздел может быть следующим. Каждый раздел должен зацикливаться на этой переменной [под блокировкой], пока она не будет соответствовать присвоенному порядковому / порядковому номеру, и увеличивать его на выходе, чтобы разрешить запуск потока, следующего в последовательности.
[*] «Блокировка билета» создается по образцу (например) пекарни, где вы «берете номер» при входе и покупаете что-то, когда на вывеске написано: «Сейчас подаем …» См.: https://en.wikipedia.org/wiki/Ticket_lock
В общем, одна из приятных особенностей блокировки билетов заключается в том, что она гарантирует прогресс и сбалансированный доступ. На практике количество повторных попыток относительно невелико / нечасто. Это особенно верно, если оно реализовано с использованием 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(amp;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(amp;lock);
if (owner == 1) {
printf("Th%d: Hellon",p);
more = 0;
owner = 1;
}
omp_unset_lock(amp;lock);
}
}
#pragma omp section
{
more = 1;
while (more) {
p = omp_get_thread_num();
omp_set_lock(amp;lock);
if (owner == 2) {
printf("Th%d: Worldn",p);
more = 0;
owner = 1;
}
omp_unset_lock(amp;lock);
}
}
#pragma omp section
{
more = 1;
while (more) {
p = omp_get_thread_num();
omp_set_lock(amp;lock);
if (owner == 3) {
printf("Th%d: Byen",p);
more = 0;
owner = 1;
}
omp_unset_lock(amp;lock);
}
}
}
omp_destroy_lock(amp;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(amp;lock2);
omp_set_lock(amp;lock2);
omp_lock_t lock3;
omp_init_lock(amp;lock3);
omp_set_lock(amp;lock3);
#pragma omp parallel sections default(shared) private(p)
{
#pragma omp section
{
p = omp_get_thread_num();
printf("Th%d: Hellon",p);
omp_unset_lock(amp;lock2);
}
#pragma omp section
{
p = omp_get_thread_num();
omp_set_lock(amp;lock2);
printf("Th%d: Worldn",p);
omp_unset_lock(amp;lock2);
omp_unset_lock(amp;lock3);
}
#pragma omp section
{
p = omp_get_thread_num();
omp_set_lock(amp;lock3);
printf("Th%d: Byen",p);
omp_unset_lock(amp;lock3);
}
}
omp_destroy_lock(amp;lock2);
omp_destroy_lock(amp;lock3);
return 0;
}