如果我有以下情况:
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
,因此循环完成了一些,其他线程在屏障上陷入僵局。然后我尝试了barrier
和flush
的各种组合和排序,没有运气(有些组合,死锁被推迟)。
如何在线程之间正确组合和同步循环条件,以便所有循环具有相同的迭代次数?
更新
我还尝试将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)。
答案 0 :(得分:1)
问题是在while循环中,您要更新cond_var两次并再次使用它,并且您需要确保这些操作不会相互干扰。每次循环迭代,代码:
因此,需要确保一个线程没有设置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约束,以达到不同次数的屏障。实际上,这可能是您的程序挂起的原因:执行额外迭代的线程在等待障碍物中的其他线程时会停滞不前。