Как правильно использовать блокировки в разделах в 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
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
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;
}

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