首先,让程序使用 1 个线程。为此,在main 中构造一个task_scheduler_init 作为第一件事,如下所示:
#include "tbb/tbb.h"
int main() {
tbb::task_scheduler_init init(1);
...
}
确保编译时将宏 TBB_USE_DEBUG 设置为 1,以便启用 TBB 的检查。
如果单线程版本有效,但多线程版本无效,请考虑使用英特尔 Inspector 来发现竞争条件。请务必使用TBB_USE_THREADING_TOOLS 进行编译,以便 Inspector 获得足够的信息。
否则,我通常首先添加断言,因为机器检查断言的速度比我读取日志消息的速度要快得多。如果我真的对断言失败的原因感到困惑,我会使用 printfs 和任务 ID(不是线程 ID)。创建任务 ID 的最简单方法是通过后递增 tbb::atomic<size_t> 并将结果存储在任务中来分配一个。
如果我今天过得很糟糕,并且 printfs 正在改变程序行为以使错误不会出现,我会使用“延迟 printfs”。将 printf 参数填充到循环缓冲区中,并在检测到故障后稍后在记录上运行 printf。通常对于缓冲区,我使用包含格式字符串和一些字长值的结构数组,并使数组大小为 2 的幂。然后一个原子增量和掩码就足以分配插槽。例如,像这样:
const size_t bufSize = 1024;
struct record {
const char* format;
void *arg0, *arg1;
};
tbb::atomic<size_t> head;
record buf[bufSize];
void recf(const char* fmt, void* a, void* b) {
record* r = &buf[head++ & bufSize-1];
r->format = fmt;
r->arg0 = a;
r->arg1 = b;
}
void recf(const char* fmt, int a, int b) {
record* r = &buf[head++ & bufSize-1];
r->format = fmt;
r->arg0 = (void*)a;
r->arg1 = (void*)b;
}
两个recf 例程记录格式和值。强制转换有点滥用,但在大多数架构上,您可以使用 printf(r->format, r->arg0, r->arg1) 在实践中正确打印记录,即使 recf 的第二次重载创建了记录。
~
~