【问题标题】:Debugging in threading building Blocks在线程构建块中调试
【发布时间】:2015-12-29 11:31:24
【问题描述】:

我想用任务编写线程构建块。但是在实践中如何调试呢?

通常,打印方法是调试程序的可靠技术。 根据我对 MPI 并行化的经验,进行日志记录的正确方法是每个线程在其自己的文件中打印其调试信息(例如“debug_irank”,其中 irank 在 MPI_COMM_WORLD 中的排名),以便可以找到逻辑错误。

如何使用 TBB 实现类似的效果?不清楚如何访问线程池中的线程号,因为这显然是 tbb 内部的东西。

或者,可以在生成任务时添加一个额外的索引来指定排名,但这会使代码相当复杂,因为整个程序都必须处理这个问题。

【问题讨论】:

标签: multithreading debugging mpi tbb


【解决方案1】:

首先,让程序使用 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-&gt;format, r-&gt;arg0, r-&gt;arg1) 在实践中正确打印记录,即使 recf 的第二次重载创建了记录。
~ ~

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多