如何在所有线程之间同步变量的值?

时间:2014-10-10 15:52:03

标签: c++ c multithreading openmp

如果我有以下情况:

bool cond_var;

#pragma omp parallel shared(cond_var)
{
    bool some_private_var;
    // ...
    do {
       #pragma omp single
       {
           cond_var = true;
       }

       // do something, calculate some_private_var;
       // ...

       #pragma omp atomic update
       cond_var &= some_private_var;

       // Syncing step
       // (???)

    } while(cond_var);

    // ... (other parallel stuff)
}

我希望我的do-while循环对我的所有线程都有相同的迭代次数,但是当我尝试#pragma omp barrier作为同步步骤(就在循环结束之前)时,我已经结束了僵局。打印cond_var的值表明某些线程将其视为true,而其他线程则将其视为false,因此循环完成了一些,其他线程在屏障上陷入僵局。然后我尝试了barrierflush的各种组合和排序,没有运气(有些组合,死锁被推迟)。

如何在线程之间正确组合和同步循环条件,以便所有循环具有相同的迭代次数?

更新

我还尝试将cond_var的值加载到另一个#pragma atomic read的私有变量,并测试该条件。它也没用。显然,原子读保证我有一致的值(旧的或新的),但不保证它是最新的。

更新2

基于代码Jonathan Dursi的代码,这是一个看起来更像我想要的MVCE 做:

#include <omp.h>
#include <cstdio>
#include <random>
#include <chrono>
#include <thread>

int main() {

    bool cond_var;
    const int nthreads = omp_get_max_threads();

    #pragma omp parallel default(none) shared(cond_var)
    {
        bool some_private_var;
        std::random_device rd;
        std::mt19937 rng(rd());
        unsigned iter_count = 0;

        /* chance of having to end: 1 in 6**nthreads; all threads must choose 0 */
        std::uniform_int_distribution<int> dice(0,5);

        const int tid = omp_get_thread_num();
        printf("Thread %d started.\n", tid);
        do {
            ++iter_count;

            #pragma omp once shared(cond_var)
            {
                // cond_var must be reset to 'true' because it is the
                // neutral element of &
                // For the loop to end, all threads must choose the
                // same random value 0
                cond_var = true;
            }

            some_private_var = (dice(rng) == 0);

            // If all threads choose 0, cond_var will remain 'true', ending the loop
            #pragma omp atomic update
            cond_var &= some_private_var;

            #pragma omp barrier
        } while(!cond_var);
        printf("Thread %d finished with %u iterations.\n", tid, iter_count);
    }

    return 0;
}

在具有足够逻辑核心的计算机中运行8个线程以同时运行所有这些线程,大多数在第一次迭代中运行死锁,尽管在第二次迭代中有一次运行正确完成(不符合1 in的机会) 1679616(6 ** 8)让所有线程选择0)。

2 个答案:

答案 0 :(得分:1)

问题是在while循环中,您要更新cond_var两次并再次使用它,并且您需要确保这些操作不会相互干扰。每次循环迭代,代码:

  1. 设置cond_var = true(使用不存在的OpenMP编译指示,“一次”,这会被忽略,因此每个线程都会这样做)
  2. 通过使用本地条件变量更新cond_var;
  3. 使用每个人更新的cond_var来测试是否退出循环。
  4. 因此,需要确保一个线程没有设置cond_var true(1)而其他线程正在设置它(2);在使用它来测试循环(3)时,没有线程仍在使用(2);当线程将其设置为true(1)时,没有线程正在测试它(3)。

    显而易见的方法是使用障碍,这三种情况之间存在障碍 - 因此存在三个障碍。所以这有效:

    #include <omp.h>
    #include <random>
    #include <chrono>
    #include <thread>
    #include <iostream>
    
    int main() {
    
        bool cond_var;
    
        #pragma omp parallel default(none) shared(cond_var,std::cout)
        {
            bool some_private_var;
            std::random_device rd;
            std::mt19937 rng(rd());
            unsigned iter_count = 0;
    
            std::uniform_int_distribution<int> dice(0,1);
    
            const int tid = omp_get_thread_num();
            printf("Thread %d started.\n", tid);
            do {
                ++iter_count;
    
                #pragma omp barrier
                #pragma omp single 
                cond_var = true;
                // implicit barrier here after the single; turned off with a nowait clause.
    
                some_private_var = (dice(rng) == 0);
    
                // If all threads choose 0, cond_var will remain 'true', ending the loop
                #pragma omp atomic update
                cond_var &= some_private_var;
    
                #pragma omp barrier
            } while(!cond_var);
    
            #pragma omp critical
            std::cout << "Thread " << tid << " finished with " << iter_count << " iterations." << std::endl;
        }
    
        return 0;
    }
    

    你可以通过让每个线程只在共享数组中设置一个局部变量,然后让一个线程执行and-ing来做得更好;所以你仍然需要两个障碍,一个是为了确保每个人都在完成之前完成,另一个是为了确保在测试完成之前完成了整个过程:

    #include <omp.h>
    #include <random>
    #include <chrono>
    #include <thread>
    #include <iostream>
    
    int main() {
    
        bool cond_var;
    
        const int num_threads = omp_get_max_threads();
        const unsigned int spacing=64/sizeof(bool);  /* to avoid false sharing */
        bool local_cond_var[num_threads*spacing];
    
        #pragma omp parallel default(none) shared(cond_var,std::cout,local_cond_var)
        {
            std::random_device rd;
            std::mt19937 rng(rd());
            unsigned iter_count = 0;
    
            std::uniform_int_distribution<int> dice(0,1);
    
            const int tid = omp_get_thread_num();
            printf("Thread %d started.\n", tid);
            do {
                ++iter_count;
    
                local_cond_var[tid*spacing] = (dice(rng) == 0);
    
                #pragma omp barrier
                #pragma omp single
                {
                    cond_var = true;
                    for (int i=0; i<num_threads; i++)
                        cond_var &= local_cond_var[i*spacing];
                }
                // implicit barrier here after the single; turned off with a nowait clause.
            } while(!cond_var);
    
            #pragma omp critical
            std::cout << "Thread " << tid << " finished with " << iter_count << " iterations." << std::endl;
        }
    
        return 0;
    }
    

    请注意,显式或隐式的障碍意味着冲洗共享变量,并向单例添加nowait子句会导致间歇性死锁。

答案 1 :(得分:0)

在循环体中的最后一个语句之后放置#pragma omp barrier并没有给我造成死锁,但这也不够。尽管工作线程将在屏障处等待,直到它们全部一起通过,但这并不能确保它们在另一侧具有一致的cond_var视图。如果在任何迭代中,更新cond_var的第一个线程将其留给true,那么这些线程中的一些或全部可以执行另一次迭代,尽管另一个线程稍后将其设置为false。只有当这些线程返回到原子更新时,他们肯定会看到其他线程写入的值。

在测试循环条件之前,您应该能够通过在屏障之后执行条件变量的原子读取来解决该问题。您需要来执行此操作或其他操作来解决问题,因为它违反了线程组中不同线程的OpenMP约束,以达到不同次数的屏障。实际上,这可能是您的程序挂起的原因:执行额外迭代的线程在等待障碍物中的其他线程时会停滞不前。