- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
*** 更新:将代码更改为重现问题的真实案例 ***
我正在处理一些使用多种多线程技术的预先存在的代码; std::thread,加上英特尔 TBB 任务组,加上 OpenMP。 🙄 看起来我在 std::thread join 中遇到了竞争条件,并且可能在 OpenMP 中也遇到了竞争条件。 (但当然这些库是由聪明人编写的,所以如果我正在使用的代码中有错误,我希望你能帮我弄清楚。)
场景是主线程启动了一堆 I/O 工作线程 std::threads,它们本身启动了一些任务,并且这些任务有一些使用 OpenMP 进行并行处理的代码段。主线程执行 std::thread::join() 等待 std::threads,然后 tbb::TaskGroup::wait() 等待任务完成。
#include <Windows.h>
#include <tbb/task_group.h>
#include <tbb/concurrent_vector.h>
#include <iostream>
#include <sstream>
#include <thread>
void DoCPUIntensiveWork(int chunkIndex);
int main ()
{
unsigned int hardwareConcurrency = 64;
tbb::concurrent_vector<std::shared_ptr<std::thread>> ioThreads;
tbb::task_group taskGroup;
wprintf(L"Starting %u IO threads\n", hardwareConcurrency);
for (unsigned int cx = 0; cx < hardwareConcurrency; ++cx)
{
ioThreads.push_back(std::shared_ptr<std::thread>(new std::thread([&taskGroup, cx]
{
wprintf(L"IO thread %u starting\r\n", GetCurrentThreadId());
// Not doing any actual IO
taskGroup.run([cx]
{
wprintf(L"CPU task %u starting\r\n", GetCurrentThreadId());
DoCPUIntensiveWork(cx);
wprintf(L"CPU task %u done\r\n", GetCurrentThreadId());
});
//Sleep(1000); Un-commenting this will make the program terminate
wprintf(L"IO thread %u done\r\n", GetCurrentThreadId());
})));
}
// Join the IO workers
for (std::shared_ptr<std::thread>& thread : ioThreads)
{
std::stringstream ss;
ss << thread->get_id();
wprintf(L"Wait for thread %S\r\n", ss.str().c_str());
thread->join(); // main thread hangs here
}
wprintf(L"IO work complete\n");
// And then wait for the CPU tasks
taskGroup.wait();
wprintf(L"CPU work complete\n");
return 0;
}
CPU 密集型工作包括 OpenMP 的使用。 (注意,如果我删除计划(静态),结果是一样的。)
// Note: I shrunk these numbers down until the amount of work is actually
// small, not CPU-intensive at all, and it still hangs
static const int GlobalBufferChunkSize = 64;
static const int NumGlobalBufferChunks = 64;
static const int StrideSize = 16;
static const int OverwriteCount = 4;
BYTE GlobalBuffer[NumGlobalBufferChunks * GlobalBufferChunkSize];
void DoCPUIntensiveWork(int chunkIndex)
{
BYTE* pChunk = GlobalBuffer + (chunkIndex * GlobalBufferChunkSize);
#pragma omp parallel for schedule(static)
for (int i = 0; i < (GlobalBufferChunkSize / StrideSize); i++)
{
BYTE* pStride = pChunk + (i * StrideSize);
for (int j = 0; j < OverwriteCount; j++)
{
memset(pStride, i, StrideSize);
}
} // Task thread hangs here
}
此代码挂起;主线程永远等待 thread->join() 。即使在只有一个 IO 作业/CPU 密集型任务的测试用例上。我添加了您在上面看到的 printf,结果显示 IO 作业完成得很快,该线程退出,然后 CPU 密集型任务在主线程甚至进入 join() 调用之前以相同的线程 ID 启动。
Starting 64 IO threads
IO thread 3708 starting
IO thread 23728 starting
IO thread 23352 starting
IO thread 3588 starting
IO thread 3708 done
IO thread 23352 done
IO thread 22080 starting
IO thread 23728 done
IO thread 3376 starting
IO thread 3588 done
IO thread 27436 starting
IO thread 10092 starting
IO thread 22080 done
IO thread 10480 starting
CPU task 3708 starting
IO thread 3376 done
IO thread 27436 done
IO thread 10092 done
IO thread 10480 done
Wait for thread 3708
... hang forever ...
线程完成后,IO 线程 ID 被重新用于任务,而 thread->join() 调用仍然坐在那里等待。当我查看调试器时,thread->join() 正在等待 ID 为 3708 的线程,并且确实存在具有该 ID 的线程,但该线程正在执行任务而不是 IO 工作。所以看起来进程的主线程实际上是在等待任务,而不是由于 ID 重用而等待 IO 线程。 (我找不到文档或代码来查看 std::thread::join() 是否基于 ID 或句柄等待,但它似乎使用了 ID,这将是一个错误。)
最佳答案
更新:关于超额认购的假设是不正确的。见 https://github.com/oneapi-src/oneTBB/issues/353
我认为这个问题可能是由 OpenMP 语义引起的。默认情况下,它总是创建与硬件并发一样多的线程。
TBB 将创建 std::thread::hardware_concurrency()
线程和 OpenMP 将创建 std::thread::hardware_concurrency()
对于从中调用它的每个 TBB 工作线程。 IE。在这个例子中,我们将有最多 std::thread::hardware_concurrency()*std::thread::hardware_concurrency()
线程(+64 IO 线程)。如果机器比较大,例如32+ 个线程,它将是 32*32 = 1024
应用程序中的线程(总体而言,它接近默认 ulimit 还是 Windows?)无论如何,在并行区域末尾使用 OpenMP 屏障语义进行如此大的超额订阅会导致非常长的执行时间(例如几分钟甚至几小时) .
为什么Sleep(1000)
帮助?我不确定,但它可能会为系统提供一些 CPU 资源以继续前进。
要检查这个想法,请添加 num_threads(1)
条款到 #pragma omp parallel for num_threads(1)
限制 OpenMP 运行时创建的线程数。
关于c++ - 线程 ID 在 std::thread 和 tbb::task_group 之间重用导致 OpenMP 中的死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66346225/
我想用 C++ 执行后序树遍历。树可能很深以至于我无法使用递归(它耗尽了堆栈)。相反,我创建了一个 std::stack将所有内容都放在堆上,并在 while 循环中遍历树,而不是使用函数调用。这很好
我想用 ppl 的 task_group 类替换普通线程的使用,但我遇到了以下问题: 我有一个 task_group 成员的 A 类, 创建类 A 的 2 个不同实例, 在第一个A实例的task_gr
我正在尝试在正在运行的任务中使用 tbb::concurrent_hash_map,但我遇到了调用 map 的 erase() 导致任务无限锁定的问题。你知道下面的代码片段有什么问题吗? #inclu
我正在使用英特尔 TBB 实现双调排序。使用 parallel_invoke 方法时一切顺利。但是当使用 task_group(不调用 wait 方法)时,输出没有排序。当如下使用 task_grou
我在 Visual C++ 2010 中使用并发运行时,并且对 parallel_invoke 的功能感兴趣和 task_group (PPL 的一部分或 Parallel Patterns Libr
我正在尝试使用 Threaded Building Blocks task_arena。有一个充满'0'的简单数组。 Arena 的线程将“1”放入数组中的奇数位置。主线程将 '2' 放入数组中的偶数
我有代码,我必须同时并行运行 parallel_for(彼此独立)。 代码是这样的: tbb::parallel_for(range1,func1());//first tbb::parallel_f
我最近对英特尔线程构建模块产生了兴趣。我想利用 tbb::task_group 类来管理线程池。 我的第一次尝试是构建一个测试,其中一个 vector 复制到另一个 vector 中:我创建了第 n
*** 更新:将代码更改为重现问题的真实案例 *** 我正在处理一些使用多种多线程技术的预先存在的代码; std::thread,加上英特尔 TBB 任务组,加上 OpenMP。 🙄 看起来我在 s
我是一名优秀的程序员,十分优秀!