gpt4 book ai didi

c++ - OpenMP任务看不到共享变量的更改

转载 作者:行者123 更新时间:2023-11-30 02:13:39 24 4
gpt4 key购买 nike

我启动了两个OpenMP任务,这些任务只是打印最初设置为1的共享变量的值。我将启动两个任务之间的变量更改为2。

我希望两个任务都能看到变量的更改后的值,即输出应为2 2。但是我总是得到1 22 1,如果变量是firstprivate,这就是我期望的结果。

我还尝试在启动任务之前设置锁定,并在注册任务和更改变量后取消设置锁定。还使两个任务都等待锁定,以确保变量已更改。结果是一样的,我不能同时执行两个任务来查看变量的值(2 2)。我怎么了?使用GCC 7.4.0。 omp_get_num_threads返回8。

#include <iostream>
#include <omp.h>

int main()
{
omp_lock_t lock;
int i = 1;
omp_init_lock(&lock);
#pragma omp parallel default(shared) shared(i)
{
#pragma omp single
{
omp_set_lock(&lock); // set lock before any tasks are registered
#pragma omp task default(shared) shared(i)
{
omp_set_lock(&lock); // should wait until lock is unset and i is 2?
std::cout << i;
omp_unset_lock(&lock);
}
i = 2;
#pragma omp task default(shared) shared(i)
{
omp_set_lock(&lock);
std::cout << i;
omp_unset_lock(&lock);
}
omp_unset_lock(&lock); // unset lock after i is set to 2
}
}
omp_destroy_lock(&lock);
return 0;
}

编辑。 也许出于某种原因 i未存储在共享内存中?如果我将其更改为无法存储在寄存器中的内容,或者将其更改为全局的,甚至只是打印其地址( std::cout << &i;),该程序就会按预期工作。可能是不确定的行为或GCC问题?

最佳答案

首先,期望对shared依赖项的排序只是在询问竞争条件。请不要这样做-这只是一种思想练习,因此您可以了解正在发生的事情。在任何实际代码中,请使用可在具有依赖关系的任务之间强制适当的数据流的依赖关系。

预期行为

预期的事件顺序为:

  • 输入omp单个
  • 创建任务1
  • 设置为i = 2
  • 创建任务2
  • 到达并行区域的末尾,现在正在等待完成
  • 的任务
  • (可能在不同的线程/内核上)任务1执行:读取i
  • (可能在不同的线程/核心上)执行任务2:读i
  • 任务已完成,程序终止

  • 当前行为

    但是,任务可能会延迟执行,因此只能保证它会在当前并行区域结束之前发生,因此您无法真正将其视为顺序程序来读取。任务也可能具有不延误的执行,即在主任务挂起的情况下立即运行。如果任务非常小或没有更多线程可用,这通常是一个不错的选择。

    根据OpenMP 4.8规范:

    undeferred task

    A task for which execution is not deferred with respect to its generating task region. That is, its generating task region is suspended until execution of the undeferred task is completed.



    因此,正在发生的事情很可能是:
  • 输入omp单个
  • 创建任务1
  • 暂停父任务以执行任务1 undeferred
  • 设置为i = 2
  • 暂停父任务以执行任务2 undefered
  • 程序终止

  • 如何修复

    相反,您应该在任务所需的数据准备就绪时运行它们:
    int main()
    {
    int i = 1;

    #pragma omp parallel
    #pragma omp single
    {
    #pragma omp task depend(in:i)
    {
    std::cout << 'a' << i;
    }

    #pragma omp task depend(out:i)
    i = 2;

    #pragma omp task depend(in:i)
    {
    std::cout << 'b' << i;
    }

    #pragma omp task depend(in:i)
    {
    std::cout << 'c' << i;
    }
    }

    return 0;
    }

    这应该始终返回 a1c2b2a1b2c2。请注意,我之所以说是因为写stdout也不是原子的,所以从理论上讲我不能排除偶发的 abc122或类似的东西。

    任务3和4仅在任务2完成后才运行,并确保正确转发数据。

    如何不解决

    创建将暂停子任务,还原父任务的锁只会使事情复杂化。

    事件的顺序变为:
  • 输入omp单个
  • 获取生成任务
  • 的锁
  • 创建任务1
  • 暂停父任务以执行任务1 undeferred
  • 暂停任务1等待锁
  • 恢复生成任务
  • 设置为i = 2
  • 暂停父任务以执行任务2 undefered
  • 暂停任务2等待锁
  • 恢复生成任务
  • 释放生成任务
  • 的锁
  • 恢复任务1,获取锁,打印1,释放锁
  • 恢复任务2,获取锁,打印2,释放锁
  • 程序终止

  • 锁不影响 i,它们只是挂起子任务,直到生成任务结束。某种形式的内存屏障/刷新可能会解决该问题,并且您还需要停止编译器针对获取和释放锁重新排序对 i的访问。实现此目的的最简单方法是使 i成为原子int:

    (请不要使用此代码)
    int main()
    {
    omp_lock_t lock;
    omp_init_lock(&lock);
    std::atomic<int> i(1);

    #pragma omp parallel shared(i)
    #pragma omp single
    {
    omp_set_lock(&lock);
    #pragma omp task shared(i)
    {
    // enter task, then suspend until i = 2
    omp_set_lock(&lock);
    std::cout << i;
    omp_unset_lock(&lock);
    }

    i = 2;

    #pragma omp task shared(i)
    {
    // enter task, then suspend until i = 2
    omp_set_lock(&lock);
    std::cout << i;
    omp_unset_lock(&lock);
    }

    // unset lock after i is set to 2 and child tasks are created
    // child tasks are possibly started and suspended at this point
    omp_unset_lock(&lock);
    }

    omp_destroy_lock(&lock);
    std::cout << std::endl;
    return 0;
    }

    但是,这是错误的方法,在任务并行性程序上使用线程并行性构造。 期望对shared依赖项的排序只是在询问竞争条件。 另外,您正在创建任务以立即将其挂起,这没有任何意义。

    使用 volatile int i,让我们看一下以下任务的程序集(来自gcc的 -S -fverbose-asm的输出)(带有 ###的行是我的评论):
    #pragma omp task shared(i)
    {
    // enter task, then suspend until i = 2
    omp_set_lock(&lock);
    __asm__ volatile("mfence":::"memory");
    std::cout << i;
    omp_unset_lock(&lock);
    }
    .LFB2346:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    .cfi_lsda 0x3,.LLSDA2346
    pushq %rbp #
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq %rsp, %rbp #,
    .cfi_def_cfa_register 6
    subq $32, %rsp #,

    ### get "omp_data_i", a struct containing _the value of i_ and the lock id
    movq %rdi, -24(%rbp) # .omp_data_i, .omp_data_i

    ### get i, store it on the stack at -4
    # lock+volatile.cc:15: #pragma omp task shared(i)
    movq -24(%rbp), %rax # .omp_data_i, tmp86
    movl 8(%rax), %eax # .omp_data_i_2(D)->i, i.6_3
    movl %eax, -4(%rbp) # i.6_3, i

    ### get the lock id and call omp_set_lock
    # lock+volatile.cc:18: omp_set_lock(&lock);
    movq -24(%rbp), %rax # .omp_data_i, tmp87
    movq (%rax), %rax # .omp_data_i_2(D)->lock, _5
    movq %rax, %rdi # _5,
    call omp_set_lock #

    ### our manually written assembly
    # lock+volatile.cc:20: __asm__ volatile("mfence":::"memory");
    #APP
    # 20 "lock+volatile.cc" 1
    mfence
    # 0 "" 2

    ### get i from the stack and call cout
    # lock+volatile.cc:21: std::cout << i;
    #NO_APP
    movl -4(%rbp), %eax # i, i.0_9
    movl %eax, %esi # i.0_9,
    movl $_ZSt4cout, %edi #,
    call _ZNSolsEi #

    ### get the lock and call unset_lock
    # lock+volatile.cc:22: omp_unset_lock(&lock);
    movq -24(%rbp), %rax # .omp_data_i, tmp88
    movq (%rax), %rax # .omp_data_i_2(D)->lock, _11
    movq %rax, %rdi # _11,
    call omp_unset_lock #

    现在,通过 int i(非 Volatile ),我们来看一下此任务的程序集:
    #pragma omp task shared(i)
    {
    // enter task, then suspend until i = 2
    omp_set_lock(&lock);
    std::cout << __atomic_load_n(&i, __ATOMIC_RELAXED);
    omp_unset_lock(&lock);
    }
    .LFB2346:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    .cfi_lsda 0x3,.LLSDA2346
    pushq %rbp #
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq %rsp, %rbp #,
    .cfi_def_cfa_register 6
    subq $16, %rsp #,

    ### get "omp_data_i", a struct containing _the address of i_ and the lock id
    movq %rdi, -8(%rbp) # .omp_data_i, .omp_data_i

    ### get the lock id and call omp_set_lock
    # lock+volatile.cc:18: omp_set_lock(&lock);
    movq -8(%rbp), %rax # .omp_data_i, tmp87
    movq (%rax), %rax # .omp_data_i_2(D)->lock, _3
    movq %rax, %rdi # _3,
    call omp_set_lock #

    ### get i and call cout
    # lock+volatile.cc:19: std::cout << __atomic_load_n(&i, __ATOMIC_RELAXED);
    movq -8(%rbp), %rax # .omp_data_i, tmp88
    movq 8(%rax), %rax # .omp_data_i_2(D)->i, _6
    movl (%rax), %eax #* _6, _9
    movl %eax, %esi # _10,
    movl $_ZSt4cout, %edi #,
    call _ZNSolsEi #

    ### get the lock id and call unset_lock
    # lock+volatile.cc:20: omp_unset_lock(&lock);
    movq -8(%rbp), %rax # .omp_data_i, tmp89
    movq (%rax), %rax # .omp_data_i_2(D)->lock, _12
    movq %rax, %rdi # _12,
    call omp_unset_lock #

    如您所见,在第一种情况下,在调用 i之前执行了在寄存器中获取 omp_set_lock的值的操作。我仅使用原子(即使具有宽松的一致性)也设法将其移动到“预期的”位置,大概是因为这样就无法相对于锁对访问进行重新排序。

    关于c++ - OpenMP任务看不到共享变量的更改,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59016886/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com