You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux下基于futex的线程等待原理及libc实现差异疑问

pthread_join的不同libc实现与futex机制分析

我尝试通过Linux的clone()创建线程并等待其结束,但Linux的wait()仅适用于进程,无法实现线程等待。为此我研究了不同libc中pthread库的实现,编写了调用pthread_join()的测试程序:

void* waited_foo(void* p)
{
    sleep(1);  //EDIT
    printf("1111\n");
    return NULL;
}

int main(int argc, char* agrv[])
{
    pthread_t tid;
    pthread_attr_t attr;

    pthread_attr_init(&attr);

    if (pthread_create(&tid, &attr, &waited_foo, NULL))
    {
        fprintf(stderr, "Error creating thread\n");
        return 1;
    }

    pthread_join(tid,NULL);
    sleep(1);
    return 0;
}

我通过strace追踪所有系统调用:

strace -f -e trace=\!brk,mmap,mprotect,munmap,rt_sigprocmask ./a.out

在musl libc的Alpine Linux上的结果:

clone(child_stack=0x7f9f63666af8, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID|0x400000strace: Process 163 attached
, parent_tid=[163], tls=0x7f9f63666b38, child_tidptr=0x7f9f636fdf90) = 163
[pid   163] nanosleep({tv_sec=1, tv_nsec=0},  <unfinished ...>
[pid   162] futex(0x7f9f63666b70, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid   163] <... nanosleep resumed>0x7f9f63666aa0) = 0
[pid   163] ioctl(1, TIOCGWINSZ, {ws_row=39, ws_col=231, ws_xpixel=0, ws_ypixel=0}) = 0
[pid   163] writev(1, [{iov_base="1111", iov_len=4}, {iov_base="\n", iov_len=1}], 21111) = 5
[pid   163] futex(0x7f9f63666b70, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid   162] <... futex resumed>)        = 0
[pid   163] <... futex resumed>)        = 1
[pid   162] futex(0x7f9f636fdf90, FUTEX_WAIT, 163, NULL <unfinished ...>
[pid   163] exit(0)                     = ?
[pid   162] <... futex resumed>)        = 0
[pid   163] +++ exited with 0 +++
nanosleep({tv_sec=1, tv_nsec=0}, 0x7fff336faca0) = 0

在GNU libc的Debian Linux上的结果:

clone(child_stack=0x7f4859865e30, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f48598669d0, tls=0x7f4859866700, child_tidptr=0x7f48598669d0) = 11383
futex(0x7f48598669d0, FUTEX_WAIT, 11383, NULLstrace: Process 11383 attached
 <unfinished ...>
[pid 11383] set_robust_list(0x7f48598669e0, 24) = 0
[pid 11383] nanosleep({tv_sec=1, tv_nsec=0}, 0x7f4859865d50) = 0
[pid 11383] write(1, "1111\n", 51111)       = 5
[pid 11383] madvise(0x7f4859066000, 8368128, MADV_DONTNEED) = 0
[pid 11383] exit(0)                     = ?
[pid 11383] +++ exited with 0 +++
<... futex resumed> )                   = 0
nanosleep({tv_sec=1, tv_nsec=0}, 0x7ffd9d9d3460) = 0

疑问解答:

  1. 为何musl libc中需要两次futex wait?
    第一次futex_wait是musl实现的用户态线程同步:pthread_join会等待线程的退出标志,线程执行完毕后主动调用futex_wake唤醒主线程。第二次futex_wait是等待内核完成CLONE_CHILD_CLEARTID的操作——当线程调用exit后,内核会将clone时指定的child_tidptr地址的值清零,musl会在这里等待该操作完成,确保线程资源完全释放,避免后续操作出现竞态。

  2. 为何glibc中仅使用一次futex_wait且无对应的futex_wake?
    glibc直接利用了Linux内核CLONE_CHILD_CLEARTID的特性:当线程终止时,内核不仅会将child_tidptr指向的内存清零,还会自动对该地址执行FUTEX_WAKE操作。因此pthread_join只需等待child_tidptr对应的futex,线程退出后内核会自动唤醒主线程,无需用户态调用futex_wake。而set_robust_list是glibc用于处理健壮互斥锁的机制,和本次线程等待无关。

  3. 为何futex_wait要使用新线程的pid作为参数?
    这里的pid实际是Linux轻量级线程的tid(线程ID)。FUTEX_WAIT的第三个参数是期望值,只有当futex地址中的值等于这个期望值时,调用才会进入阻塞等待。这样做是为了避免竞态:如果在调用futex_wait之前,线程已经退出,child_tidptr的值已经被内核清零,此时futex_wait会发现值不等于tid,直接返回,不会无意义地阻塞,保证同步逻辑的正确性。

内容的提问来源于stack exchange,提问作者xperious

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 11:20:29