OpenMP 创建了太多线程

OpenMP creates too many threads

我不确定为什么 OpenMP 使用这么多线程。它似乎与 Microsoft 实现无关,因为我也尝试过显示相同行为的 Intel 库。 我的代码中有一些并行部分是受计算限制的,不应创建和使用比我拥有的内核更多的线程。但我观察到的是,对于 n 个启动线程,OpenMP 创建了 n*Cores 线程。对我来说,这看起来像是一个大线程泄漏。

如果我在服务器上执行 "small" 32 位应用程序 运行 它可能会失败,因为 1000 个 OpenMP 线程需要 2 GB 地址 space 已经没有为应用程序留下内存。那不应该发生。我希望最先进的线程池能够重用其线程并删除不再使用的线程。

我曾尝试使用 omp_set_num_threads(8) 将线程池大小限制为 8 个内核,但这似乎只限制了每个启动线程实例的线程数。是我做错了还是 OpenMP 不应该以这种方式使用?

在我的 8 核机器上,我的 AsyncWorker 中的 5 个启动线程 class 将分配由 OpenMP 创建的 38 个线程。我希望只创建 8 个线程,并且应该在所有 5 个启动线程中重复使用这些线程。

#include <atomic>
#include <thread>
#include <omp.h>
#include <chrono>
#include <vector>
#include <memory>

class AsyncWorker {
private:
    std::vector<std::thread> threads;

public:
    AsyncWorker()
    {
    }

    void start() // add one thread that starts an OpenMP parallel section
    {
        threads.push_back(std::thread(&AsyncWorker::threadFunc, this));
    }

    ~AsyncWorker()
    {
        for (auto &t : threads)
        {
            t.join();
        }
    }

private:
    void threadFunc()
    {
        std::atomic<int> counter;

        auto start = std::chrono::high_resolution_clock::now();
        std::chrono::milliseconds durationInMs;

        while (durationInMs.count() <5000l) 
        {
        // each instance seems to get its own thread pool. 
        // Why? And how can I limit the threadpool to the number of cores and when will the threads be closed?
#pragma omp parallel  
            {
                counter++;
                auto stop = std::chrono::high_resolution_clock::now();
                durationInMs = std::chrono::duration_cast<std::chrono::milliseconds>(stop - start);
            }
        }
    }

};

int main() {
    //omp_set_dynamic(0);
    //omp_set_nested(0);
    //omp_set_num_threads(8);

    {
        AsyncWorker foo;

        foo.start();  // 1
        foo.start();  // 2 
        foo.start();  // 3
        foo.start();  // 4
        foo.start();  // 5

        system("pause");
    }

    return 0;
}

OpenMP 使用的线程数是按并​​行部分设置的,您同时生成 5 个并行部分。因此你得到 40 个线程。

您似乎在寻找基于任务的并行性。在 OpenMP 中,您可以通过启动并行区域然后根据​​需要创建任务来实现这一点。从我的头脑来看,这个模式的代码是这样写的:

// Start parallel region
#pragma omp parallel
{
  // Only let a single thread create the tasks
  #pragma omp single
  {
     for(int i = 0; i < 40; i++)
     {
       // Actually create the task that needs to be performed
       #pragma omp task
       {
         heavy_work();
       }
     }
  }
}

这样您将只有 8 个线程并行工作。

OpenMP 不适合以这种方式使用。混合使用 OpenMP 和其他线程方法将导致灾难,除非非常小心。即便如此,结果也是不可预测的。 OpenMP 标准有意避免定义此类互操作性,并且供应商可以自由地以他们认为合适的方式提供它(如果他们认为合适)。

omp_set_num_threads(8) 并不像您认为的那样。当不存在 num_threads() 子句时,它设置当前线程遇到的并行区域的线程数。此外,omp_set_nested(0) 没有(或可能)没有影响,因为您不是从 OpenMP 线程而是从 C++11 线程启动并行区域。可以通过 OMP_THREAD_LIMIT 环境变量对 OpenMP 线程总数设置全局限制,但这仅在 OpenMP 3.0 及更高版本中可用,而 MSVC(永远?)停留在 OpenMP 2.0 时代。

可能的行动方案是:

  • 使用共享队列结构和在循环中旋转的 OpenMP 线程为 OpenMP 2.0 执行您自己的任务,使工作项出队
  • 用 Intel Threading Building Blocks 替换 OpenMP - 它是开源的,支持 Windows、Linux、OS X 和 Android
  • Concurrency Runtime 中的 Microsoft PPL 替换 OpenMP,它基本上提供了 TBB
  • 的不可移植子集