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

Я должен распараллелить следующий код с двумя блокировками, и вывод должен быть в порядке:
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;
}

Да, я могу использовать только разделы, и я должен использовать ровно 2 замка.

BadBoy21 20.12.2020 23:26

Ваша блокировка не гарантирует порядок ваших тем — только атомарный доступ к 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 должен быть приватным

Craig Estey 21.12.2020 00:44
Почему в Python есть оператор &quot;pass&quot;?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
Toor - Ангулярный шаблон для бронирования путешествий
Toor - Ангулярный шаблон для бронирования путешествий
Toor - Travel Booking Angular Template один из лучших Travel & Tour booking template in the world. 30+ валидированных HTML5 страниц, которые помогут...
0
2
279
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Из моих топовых комментариев:

Ваша блокировка не гарантирует порядок ваших тем — только атомарный доступ к 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;
}

Другие вопросы по теме