防止线程不必要的退出并使池保持活动状态
Posted
技术标签:
【中文标题】防止线程不必要的退出并使池保持活动状态【英文标题】:Preventing threads from unnecessary exit and keep the pool alive 【发布时间】:2021-03-27 05:03:03 【问题描述】:假设我用OMP_NUM_THREADS=16
调用一个程序。
第一个函数调用#pragma omp parallel for num_threads(16)
。
第二个函数调用#pragma omp parallel for num_threads(2)
。
第三个函数调用#pragma omp parallel for num_threads(16)
。
使用gdb
进行调试告诉我,在第二次调用14
时线程退出。在第三次调用中,14
新线程被生成。
是否可以防止14
线程在第二次调用时退出?谢谢。
证明清单如下。
$ cat a.cpp
#include <omp.h>
void func(int thr)
int count = 0;
#pragma omp parallel for num_threads(thr)
for(int i = 0; i < 10000000; ++i)
count += i;
int main()
func(16);
func(2);
func(16);
return 0;
$ g++ -o a a.cpp -fopenmp -g
$ ldd a
...
libgomp.so.1 => ... gcc-9.3.0/lib64/libgomp.so.1
...
$ OMP_NUM_THREADS=16 gdb a
...
Breakpoint 1, main () at a.cpp:13
13 func(16);
(gdb) n
[New Thread 0xffffbe24f160 (LWP 27216)]
[New Thread 0xffffbda3f160 (LWP 27217)]
[New Thread 0xffffbd22f160 (LWP 27218)]
[New Thread 0xffffbca1f160 (LWP 27219)]
[New Thread 0xffffbc20f160 (LWP 27220)]
[New Thread 0xffffbb9ff160 (LWP 27221)]
[New Thread 0xffffbb1ef160 (LWP 27222)]
[New Thread 0xffffba9df160 (LWP 27223)]
[New Thread 0xffffba1cf160 (LWP 27224)]
[New Thread 0xffffb99bf160 (LWP 27225)]
[New Thread 0xffffb91af160 (LWP 27226)]
[New Thread 0xffffb899f160 (LWP 27227)]
[New Thread 0xffffb818f160 (LWP 27228)]
[New Thread 0xffffb797f160 (LWP 27229)]
[New Thread 0xffffb716f160 (LWP 27230)]
15 func(2);
(gdb)
[Thread 0xffffba9df160 (LWP 27223) exited]
[Thread 0xffffb716f160 (LWP 27230) exited]
[Thread 0xffffbca1f160 (LWP 27219) exited]
[Thread 0xffffb797f160 (LWP 27229) exited]
[Thread 0xffffb818f160 (LWP 27228) exited]
[Thread 0xffffbd22f160 (LWP 27218) exited]
[Thread 0xffffb899f160 (LWP 27227) exited]
[Thread 0xffffbda3f160 (LWP 27217) exited]
[Thread 0xffffbb1ef160 (LWP 27222) exited]
[Thread 0xffffb91af160 (LWP 27226) exited]
[Thread 0xffffba1cf160 (LWP 27224) exited]
[Thread 0xffffb99bf160 (LWP 27225) exited]
[Thread 0xffffbb9ff160 (LWP 27221) exited]
[Thread 0xffffbc20f160 (LWP 27220) exited]
17 func(16);
(gdb)
[New Thread 0xffffbb9ff160 (LWP 27231)]
[New Thread 0xffffbc20f160 (LWP 27232)]
[New Thread 0xffffb99bf160 (LWP 27233)]
[New Thread 0xffffba1cf160 (LWP 27234)]
[New Thread 0xffffbda3f160 (LWP 27235)]
[New Thread 0xffffbd22f160 (LWP 27236)]
[New Thread 0xffffbca1f160 (LWP 27237)]
[New Thread 0xffffbb1ef160 (LWP 27238)]
[New Thread 0xffffba9df160 (LWP 27239)]
[New Thread 0xffffb91af160 (LWP 27240)]
[New Thread 0xffffb899f160 (LWP 27241)]
[New Thread 0xffffb818f160 (LWP 27242)]
[New Thread 0xffffb797f160 (LWP 27243)]
[New Thread 0xffffb716f160 (LWP 27244)]
19 return 0;
【问题讨论】:
【参考方案1】:简单的答案是 GCC 不可能强制运行时保留线程。通过粗略阅读source code of libgomp
,没有可移植或特定于供应商的 ICV 可以防止终止连续区域中多余的空闲线程。 (如果我错了,有人纠正我)
如果您确实需要依赖 unportable 要求,即 OpenMP 运行时使用跨区域的持久线程,并且团队规模各不相同,那么请使用 Clang 或 Intel C++ 而不是 GCC。 Clang 的(实际上是 LLVM 的)OpenMP 运行时基于英特尔的开源版本,它们的行为都符合您的要求。同样,这不是可移植的,并且行为可能会在将来的版本中发生变化。相反,建议不要以这样一种方式编写代码,即其性能取决于 OpenMP 实现的特殊性。例如,如果循环比创建线程组花费的时间多几个数量级(在现代系统上大约为几十微秒),那么运行时是否使用持久线程并不重要。
如果 OpenMP 开销确实是个问题,例如,如果在循环中完成的工作不足以分摊开销,那么可移植的解决方案是提升并行区域,然后重新实现 for
工作共享结构,例如在@dreamcrash 的答案中或(ab)通过设置块大小来使用 OpenMP 的循环调度,该块大小只会导致所需数量的线程处理问题:
#include <omp.h>
void func(int thr)
static int count;
const int N = 10000000;
int rem = N % thr;
int chunk_size = N / thr;
#pragma omp single
count = 0;
#pragma omp for schedule(static,chunk_size) reduction(+:count)
for(int i = 0; i < N-rem; ++i)
count += i;
if (rem > 0)
#pragma omp for schedule(static,1) reduction(+:count)
for(int i = N-rem; i < N; ++i)
count += i;
#pragma omp barrier
int main()
int nthreads = max of 16, 2, other values of thr;
#pragma omp parallel num_threads(nthreads)
func(16);
func(2);
func(16);
return 0;
您需要所有线程中大小完全相同的块。第二个循环用于处理thr
不划分迭代次数的情况。此外,不能简单地对私有变量求和,因此必须共享count
,例如,将其设为static
。这很丑陋,并且拖累了一堆同步必需品,这些同步必需品的开销可能与生成新线程相当,并使整个练习毫无意义。
【讨论】:
嗨 Histro,我认为你可以让 count 成为私有并且仍然可以减少,例如我所做的,当然最后它需要某种线程之间的共享结构跨度> 感谢您的回答!我最近更多地解决了这个问题。我尝试了两种变体:1)。使用clang libomp和2)。使用 num_threads(max_thrs) 和适当数量的块(nthr=2 或 nthr=16),即就像在您和 @dreamcrash 的代码中一样。有趣的是,与我的初始版本相比,这两种变体的性能都较差(我的意思是迭代次数等于 nthr) 我的意思是#pragma parallel for num_threads(nthr) for(int i=0;i 顺便说一句,在这两种变体中(使用 clang-libomp 和使用 num_threads(MAX_THRS)+gcc-ligomp)——我用 gdb 检查了线程确实只在最后才被杀死该程序。你知道为什么会这样吗? @dreamcrash 对不起,我的意思是,在上面我的 cmets 中的所有#pragmas 中,循环是 for(int i=0;i 【参考方案2】:一种方法是创建一个parallel region
,过滤掉将执行for的线程,然后手动分配循环迭代每个 线程。为简单起见,我假设parallel for schedule(static, 1)
:
include <omp.h>
void func(int total_threads)
int count = 0;
int thread_id = omp_get_thread_num();
if (thread_id < total_threads)
for(int i = thread_id; i < 10000000; i += total_threads)
count += i;
#pragma omp barrier
int main()
...
#pragma omp parallel num_threads(max_threads_to_be_used)
func(16);
func(2);
func(16);
return 0;
请记住,必须修复 count += i;
的竞争条件。在原始代码中,您可以使用 reduction 子句轻松修复它,即#pragma omp parallel for num_threads(thr) reduction(sum:count)
。在带有手册 for 的代码中,您可以按如下方式解决它:
#include <omp.h>
#include<stdio.h>
#include <stdlib.h>
int func(int total_threads)
int count = 0;
int thread_id = omp_get_thread_num();
if (thread_id < total_threads)
for(int i = thread_id; i < 10000000; i += total_threads)
count += i;
return count;
int main()
int max_threads_to_be_used = // the max that you want;
int* count_array = malloc(max_threads_to_be_used * sizeof(int));
#pragma omp parallel num_threads(max_threads_to_be_used)
int count = func(16);
count += func(2);
count += func(16);
count_array[omp_get_thread_num()] = count;
int count = 0;
for(int i = 0; i < max_threads_to_be_used; i++)
count += count_array[i];
printf("Count = %d\n", count);
return 0;
我会说大多数时候,每个并行区域中使用的线程数相同。所以这种类型的模式不应该是一个经常发生的问题。
【讨论】:
以上是关于防止线程不必要的退出并使池保持活动状态的主要内容,如果未能解决你的问题,请参考以下文章