libgomp持续创建新线程的原因及线程重建场景问询
我在CentOS 8.5系统上运行基于gcc和libgomp的OpenMP程序,通过strace工具追踪发现clone系统调用被反复触发(以下为部分日志)。由于其他非OpenMP线程数量固定且均在主函数初始阶段完成初始化,因此判断OpenMP线程在被持续重建。但我编写的简单OpenMP程序中,OpenMP会在初始化阶段创建线程池并复用线程,特此问询:libgomp线程会在哪些场景下终止并重建?
clone(child_stack=0x7f16bff89ef0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tid=[3265184], tls=0x7f16bff8f700, child_tidptr=0x7f16bff8f9d0) = 3265184 sched_setaffinity(3265184, 16, [8]) = 0 futex(0x7f16bff8fd18, FUTEX_WAKE_PRIVATE, 1) = 1 futex(0x40083f14, FUTEX_WAKE_PRIVATE, 2147483647) = 0 futex(0x40083f14, FUTEX_WAKE_PRIVATE, 2147483647) = 0 futex(0x40083f14, FUTEX_WAKE_PRIVATE, 2147483647) = 0 futex(0x4012f184, FUTEX_WAKE_PRIVATE, 2147483647) = 0 futex(0x4012f184, FUTEX_WAKE_PRIVATE, 2147483647) = 0 clone(child_stack=0x7f16c1f8def0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tid=[3265185], tls=0x7f16c1f93700, child_tidptr=0x7f16c1f939d0) = 3265185 sched_setaffinity(3265185, 16, [2]) = 0 futex(0x7f16c1f93d18, FUTEX_WAKE_PRIVATE, 1) = 1 clone(child_stack=0x7f16c078aef0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tid=[3265186], tls=0x7f16c0790700, child_tidptr=0x7f16c07909d0) = 3265186 sched_setaffinity(3265186, 16, [4]) = 0 futex(0x7f16c0790d18, FUTEX_WAKE_PRIVATE, 1) = 1 clone(child_stack=0x7f16bff89ef0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tid=[3265187], tls=0x7f16bff8f700, child_tidptr=0x7f16bff8f9d0) = 3265187 sched_setaffinity(3265187, 16, [6]) = 0 futex(0x7f16bff8fd18, FUTEX_WAKE_PRIVATE, 1) = 1 clone(child_stack=0x7f16c178cef0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tid=[3265188], tls=0x7f16c1792700, child_tidptr=0x7f16c17929d0) = 3265188 sched_setaffinity(3265188, 16, [8]) = 0 futex(0x7f16c1792d18, FUTEX_WAKE_PRIVATE, 1) = 1 futex(0x40083f14, FUTEX_WAKE_PRIVATE, 2147483647) = 0 futex(0x40083f14, FUTEX_WAKE_PRIVATE, 2147483647) = 0 futex(0x40083f14, FUTEX_WAKE_PRIVATE, 2147483647) = 0 clone(child_stack=0x7f16c1f8def0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tid=[3265189], tls=0x7f16c1f93700, child_tidptr=0x7f16c1f939d0) = 3265189 sched_setaffinity(3265189, 16, [2]) = 0 futex(0x7f16c1f93d18, FUTEX_WAKE_PRIVATE, 1) = 1 clone(child_stack=0x7f16c178cef0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tid=[3265190], tls=0x7f16c1792700, child_tidptr=0x7f16c17929d0) = 3265190 sched_setaffinity(3265190, 16, [4]) = 0
环境变量:
export PARALLEL_ENSEMBLE_THREADS=5 export GOMP_CPU_AFFINITY=7,2,4,6,8
常见触发libgomp线程终止重建的场景
线程亲和性配置变更:你设置了
GOMP_CPU_AFFINITY,如果程序运行过程中亲和性规则被动态修改,或者线程绑定的CPU核心出现不可用(比如被系统离线),libgomp可能会销毁旧线程并重建符合新亲和性要求的线程。从你的strace日志看,每次clone后都调用sched_setaffinity绑定特定核心,不排除存在亲和性配置的动态调整逻辑。线程池大小动态调整:如果程序中通过
omp_set_num_threads()动态修改线程数量,或者环境变量OMP_NUM_THREADS被动态变更,libgomp会根据新的线程数调整线程池——销毁多余线程,或者新建不足的线程。若你的程序在不同并行区域反复修改线程数量,就会导致频繁的线程重建。线程意外退出:OpenMP线程如果遇到未捕获的信号、访问非法内存等异常情况而意外终止,libgomp会在后续并行区域执行时重建新的线程来补充线程池。可以检查程序是否存在未处理的异常,或者并行区域内的代码是否有导致线程崩溃的逻辑。
嵌套并行的特殊处理:当启用嵌套并行时,libgomp可能会为内层并行区域创建临时线程,内层并行结束后销毁这些线程。如果程序存在大量嵌套并行操作,且内层并行的线程需求频繁变化,也会出现反复clone的情况。
libgomp的空闲线程超时销毁机制:libgomp默认会在线程空闲一段时间后销毁线程,避免资源占用。如果你的程序并行区域之间间隔较长,空闲线程被销毁后,下次进入并行区域时就需要重新创建线程。可以通过设置环境变量
GOMP_SPINCOUNT延长线程空闲等待时间,观察是否改善。资源限制触发的线程重建:如果系统的线程资源(如栈内存、进程描述符)出现临时不足,导致线程创建失败,libgomp可能会在资源恢复后尝试重建线程;或者程序中存在线程栈溢出的情况,导致线程崩溃后重建。
内容的提问来源于stack exchange,提问作者Xiaoyong Guo

