首页 > 解决方案 > 防止线程不必要的退出并使池保持活动状态

问题描述

假设我用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;

标签: c++cmultithreadingopenmpthreadpool

解决方案


简单的答案是 GCC 不可能强制运行时保留线程。通过粗略阅读 的源代码libgomp,没有可移植或特定于供应商的 ICV 可以防止终止连续区域中多余的空闲线程。(如果我错了,有人纠正我)

如果您确实需要依赖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必须共享,例如,通过 make it static。这很丑陋,并且拖累了一堆同步必需品,这些必需品的开销可能与产生新线程的开销相当,并使整个练习毫无意义。


推荐阅读