使用clone创建带CLONE_THREAD标识的线程如何实现阻塞等待退出
问题根因
设置CLONE_THREAD标志后,通过clone创建的执行流会和调用方属于同一个线程组,共享同一个进程ID,内核不会将其识别为独立子进程,也不会向父进程发送退出通知,因此waitpid等所有wait族系统调用都无法捕获该执行流的退出事件,返回-1是符合设计逻辑的行为。
解决方案
因为你后续需要转汇编、不能依赖C标准库,所以可以直接通过futex + CLONE_CHILD_CLEARTID标志的组合实现无依赖的线程退出等待,无需引入额外库函数。逻辑如下:
- 定义一个共享的整型变量作为退出标志
- 调用
clone时新增CLONE_CHILD_CLEARTID标志,同时在可选参数末尾传入该退出标志的地址,内核会自动将新线程的TID写入该地址,在线程退出时自动将地址值清0并唤醒所有等待该地址的执行流 - 主线程通过
futex系统调用阻塞等待该标志变化即可
修改后的可运行代码
#include <sys/wait.h> #include <sched.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/mman.h> #include <linux/futex.h> #include <sys/syscall.h> int globalValue=0; // 共享的线程退出标志和返回值存储 volatile int thread_exited = 0; int thread_retval = 0; static int childFunc(void*arg) { printf("Global value is %d\n", globalValue); globalValue += *(int*)&arg; thread_retval = 31; return 31; } // 简单的futex等待封装,转汇编时直接替换为对应架构的系统调用即可 static void futex_wait(volatile int *addr, int expect) { syscall(SYS_futex, addr, FUTEX_WAIT, expect, NULL, NULL, 0); } int main(int argc, char *argv[]) { auto stack_size = 1024 * 1024; auto stack = (char*)mmap(NULL, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); if (stack == MAP_FAILED) { perror("mmap"); exit(EXIT_FAILURE); } globalValue = 5; // 新增CLONE_CHILD_CLEARTID标志,末尾传入退出标志地址 auto pid = clone(childFunc, stack + stack_size, CLONE_THREAD|CLONE_VM|CLONE_SIGHAND|CLONE_CHILD_CLEARTID, (void*)7, NULL, NULL, &thread_exited); sleep(1); //避免printf输出冲突,转汇编时可根据需求删除 if (pid == -1) { perror("clone"); exit(EXIT_FAILURE); } printf("clone() returned %d\n", pid); // 阻塞等待线程退出,替代原waitpid调用 futex_wait(&thread_exited, pid); printf("Expecting 12 got %d. Expecting 31 got %d.\n", globalValue, thread_retval); return 0; }
转汇编注意事项
两种系统调用的参数非常简单,仅需传递地址、标志两个核心参数,不同架构的系统调用号可直接参考内核源码头文件:
- x86_64架构:
SYS_futex编号为202 - arm64架构:
SYS_futex编号为240
不需要依赖任何C标准库的封装,直接按系统调用规则传参即可实现完整功能。
内容的提问来源于stack exchange,提问作者Eric Stotch
相关产品推荐
相关产品推荐

