Доступ к закрытым переменным исполняющего потока в задаче в OpenMP - PullRequest
0 голосов
/ 11 марта 2020

Я пытаюсь изучить OpenMP, и наткнулся на тот факт, что потоки не сохраняют свои собственные данные при выполнении задач, а скорее имеют копию данных потока, который сгенерировал задачу. Позвольте мне продемонстрировать это на примере:

#include <stdio.h>
#include <unistd.h>
#include <omp.h>

int main()
{
#pragma omp parallel num_threads(4)
    {
        int thread_id = omp_get_thread_num();
#pragma omp single
        {
            printf("Thread ID of the #single: %d\n", omp_get_thread_num());
            for (int i = 0; i < 10; i++) {
#pragma omp task
                {
                    sleep(1);
                    printf("thread_id, ID of the executing thread: %d, %d\n", thread_id, omp_get_thread_num());
                }
            }
        }
    }

    return 0;
}

Пример вывода этого кода следующий:

Thread ID of the #single: 1
thread_id, ID of the executing thread: 1, 2
thread_id, ID of the executing thread: 1, 0
thread_id, ID of the executing thread: 1, 3
thread_id, ID of the executing thread: 1, 1
...

Очевидно, что thread_id в задаче относится к копия, которая назначена на thread_id потока, который создал задачу (т. е. на тот, который выполняет одну часть кода).

Что если бы я хотел сослаться на выполняющуюся собственные переменные потока тогда? Они безнадежно затенены? Есть ли пункт для вывода этого кода number, same number вместо конца каждой строки?

Ответы [ 2 ]

2 голосов
/ 11 марта 2020

Я пытаюсь изучить OpenMP, и наткнулся на тот факт, что потоки не сохраняют свои собственные данные при выполнении задач, а скорее имеют копию данных потока, который сгенерировал задачу.

«[T] Hreads не сохраняют свои собственные данные» - странный способ описать это. Приписывание владения данными самим потокам, а не задачам, которые они выполняют, является, пожалуй, ключевой концептуальной проблемой. Совершенно естественно, и следует ожидать, что поток, выполняющий заданную задачу, работает с и в среде данных этой задачи .

Но если вы не привыкли к явным задачам, то Понятно, что вы ушли так далеко, не оценивая различие здесь. (Многие) конструкции, которые приводят к неявным задачам, обычно структурированы таким образом, что не поддаются обнаружению различий.

Так что с вашим примером, да,

thread_id внутри задачи относится к копии, которая назначена thread_id потока, который создал задачу (т. е. тот, который выполняет единственную часть кода).

Хотя это может и не быть быть сразу очевидным, что следует из спецификации OMP:

Когда поток встречает конструкцию задачи, из кода для связанного структурированного блока генерируется явная задача. Среда данных задачи создается в соответствии с разделами атрибутов совместного использования данных в конструкции задачи, ICV среды для данных и любыми применяемыми по умолчанию значениями .

( Спецификация OMP 5.0, раздел 2.10.1 ; выделение добавлено)

Единственный способ, которым можно удовлетворить, - это если задача закрывается над какими-либо общими данными из контекста ее объявления, что действительно является вы наблюдаете Более того, это, как правило, то, что нужно - данные, с которыми должна работать задача , должны быть установлены в точке и в контексте ее объявления, иначе как можно указать, к чему относится задача? do?

Что если я хотел бы сослаться на собственные закрытые переменные исполняющего потока?

Потоки не имеют переменных, по крайней мере, не в терминология OMP. Они принадлежат «среде данных» любых задач, которые они выполняют в любой момент времени.

Являются ли они невосстановимыми тенями?

Когда поток выполняет заданную задачу , он обращается к среде данных этой задачи. Эта среда может включать переменные, которые используются совместно с другими задачами, но только в этом смысле она может обращаться к переменным другой задачи. «Неоправданно затененный» - это не та формулировка, которую я бы использовал для описания ситуации, но она дает представление о том, что нужно сделать. каждая строка?

Есть способы реструктурировать код для достижения этой цели, но ни один из них не так прост, как просто добавление предложения в директиву omp task. На самом деле, я не думаю, что какой-либо из них вообще связан с явными задачами. Самый естественный способ получить это - параллель l oop:

#include <stdio.h>
#include <unistd.h>
#include <omp.h>

int main(void) {
    #pragma omp parallel for num_threads(4)
    for (int i = 0; i < 10; i++) {
        int thread_id = omp_get_thread_num();

        sleep(1);
        printf("thread_id, ID of the executing thread: %d, %d\n", thread_id, omp_get_thread_num());
    }

    return 0;
}

Конечно, это также упрощает его до такой степени, что это кажется тривиальным, но, возможно, это помогает понять суть. Большая часть цели объявления явной задачи состоит в том, что эта задача может быть выполнена другим потоком, чем тот, который ее создал, и это именно то, чего вам нужно избегать, чтобы достичь требуемого поведения.

0 голосов
/ 11 марта 2020

Проблема в том, что здесь вы создаете четыре параллельных потока:

#pragma omp parallel num_threads(4)

, а здесь вы ограничиваете дальнейшее выполнение одним отдельным потоком

#pragma omp single
    {
        printf("Thread ID of the #single: %d\n", omp_get_thread_num());

С этого момента только используется контекст этого единственного потока, следовательно, используется тот же экземпляр переменной thread_id. Здесь

        for (int i = 0; i < 10; i++) {
#pragma omp task
            {
                sleep(1);
                printf("thread_id, ID of the executing thread: %d, %d\n", thread_id, omp_get_thread_num());
            }

вы действительно распределяете итерацию l oop по четырем потокам, но в зависимости от состояния отдельной задачи (вместе с соответствующим экземпляром thread_id, для которого Вы ограничили выполнение выше. Итак, первая мера - завершить секцию single непосредственно после printf (до начала итераций l oop):

    int thread_id = omp_get_thread_num();
#pragma omp single
    {
        printf("Thread ID of the #single: %d\n", omp_get_thread_num());
    }

    // Now outside the "single"
    for (int i = 0; i < 10; i++) {
        ...

Теперь для каждой итерации в for l oop создается задача немедленно . И это выполняется для каждого из четырех потоков. Итак, теперь у вас есть 40 задач, ожидающих с

  • 10 x thread_id == 0
  • 10 x thread_id == 1
  • 10 x thread_id == 2
  • 10 x thread_id == 3

Эти задачи теперь произвольно распределяются между потоками. Вот где связь между thread_id и номером потока omp теряется. С этим ничего не поделаешь, кроме удаления

#pragma omp task

, что приводит к аналогичному результату (с соответствующим идентификатором потока omp и thread_id числа), но внутренне работает несколько иначе (разобщение задач и потоков omp не происходит).

...