Как правильно использовать блокировки в разделах в OpenMP?

#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;
}