- 标准库
- 操作系统
- 语言运行时
- 系统编程
【免费下载链接】cosmopolitan
build-once run-anywhere c library
本文以 libc/thread/README.md 为核心,系统讲解 Cosmopolitan Libc 的线程子系统:它以 Open Group Base Specifications Issue 7(IEEE Std 1003.1-2017,即 POSIX.1-2017)为蓝本实现 pthread 接口,并叠加大量 GNU 扩展。读者将掌握该库的线程生命周期模型、pthread_create底层跨平台调用链、锁与条件变量的 nsync 实现,以及属性配置、汇合、取消等完整实战用法。
标准依据与实现范围
Cosmopolitan 的线程库把「符合标准」当作第一原则。README.md 明确写道:线程能力是按照 The Open Group Base Specifications Issue 7、2018 版(IEEE Std 1003.1-2017,即 IEEE Std 1003.1-2008 的修订版)实现的,同时还包括 GNU 扩展。
这意味着项目不是简单搬运某一家系统的 pthread,而是:
- 以 POSIX.1-2017 为行为基准,保证 API 语义(含
pthread_join的自汇合EDEADLK、汇合取消等边界行为)有标准可依; - 在标准之上提供 GNU/BSD 风格的
_np(non-portable)扩展函数,用于补充调度、命名、定时汇合等实用能力; - 在同一套 API 之下,把线程落地到 Linux、FreeBSD、OpenBSD、NetBSD、macOS(XNU)、Windows NT 乃至裸机(Metal)等多种平台。
整个子系统位于 libc/thread 目录,约 150 个源文件,覆盖 POSIX 线程(pthread_*)、C11 线程(thrd_*/mtx_*/cnd_*/tss_*,见 threads.h)与 POSIX 信号量(sem_*,见 semaphore.h)三套 API。
目录结构与构建方式
BUILD.mk 把线程库打包为LIBC_THREAD包,产物为o/$(MODE)/libc/thread/thread.a。从依赖关系可以清晰看出线程库的层次:
LIBC_CALLS/LIBC_SYSV_CALLS:系统调用层(clone、futex、sigprocmask等);LIBC_NT_KERNEL32/LIBC_NT_SYNCHRONIZATION:Windows 侧CreateThread、WaitForSingleObject等;THIRD_PARTY_NSYNC:核心同步原语(互斥量、条件变量的实现基础);THIRD_PARTY_DLMALLOC:线程私有堆分配器;THIRD_PARTY_LIBUNWIND:栈回溯与异常展开。
编译时对线程源文件统一关闭 sanitizer,并强制栈帧不超过 4 KiB(-Wframe-larger-than=4096、-Walloca-larger-than=4096),这保证了线程栈(最小 32 KiB)不会被深递归或大 alloca 击穿。
线程对象与生命周期状态机
每个线程由 posixthread.internal.h 中的struct PosixThread描述,字段包括:
pt_start/pt_val:入口函数与其参数(也是退出返回值容器);pt_tls/tib:TLS 分配的底部地址与CosmoTib线程信息块(线程 ID、信号掩码、futex 地址、malloc 句柄等);pt_status:原子化的线程状态;pt_refs:引用计数,防止被「收割」;pt_cleanup:清理回调栈;pt_exiter[5]:setjmp缓冲区,供pthread_exit跳回入口框架;pt_attr:线程属性快照。
该头文件以 ASCII 状态图定义了合法迁移(源码注释原文):
LEGAL TRANSITIONS ┌──> TERMINATED ─┐ pthread_create ─┬─> JOINABLE ─┴┬─> DETACHED ───┴─> ZOMBIE └──────────────┘四种状态语义如下:
| 状态 | 含义 | 后继迁移 |
|---|---|---|
kPosixThreadJoinable | 运行中、等待被 join 的可汇合线程 | 入口返回/pthread_exit→Terminated;pthread_detach→Detached;他人fork()→Zombie |
kPosixThreadDetached | 由库自动回收的托管线程 | 入口返回/pthread_exit→Zombie;他人fork()→Zombie |
kPosixThreadTerminated | 已终止的可汇合线程 | pthread_join→ 释放;pthread_detach→Zombie;他人fork()→Zombie |
kPosixThreadZombie | 已终止的分离线程 | 在pthread_create()入口或 atexit 处理器中择机_pthread_free() |
僵尸线程的回收由_pthread_decimate()完成:在获取线程库 GIL 后,遍历全局双向链表_pthread_list,跳过仍有引用(pt_refs > 0,例如pthread_kill持有租约)或仍在占用栈(tib_ctid非零)的线程,把符合阈值的僵尸批量摘除,并在释放 GIL 之后才执行munmap/free——源码注释称之为「death is a release and not a punishment」。_pthread_free()负责归还自管栈、销毁缓存的 nsync waiter、关闭 Windows 句柄或 XNU 的__pthread_join系统线程句柄、释放线程私有堆与 TLS 内存。每次pthread_create()入口都会先触发一次_pthread_decimate(kPosixThreadZombie),避免僵尸无限累积。
pthread_create:跨平台的 clone 多态
pthread_create.c 是线程库的枢纽。其内部流程可概括为:
- 记录旧 errno,调用
_pthread_decimate清理僵尸; BLOCK_SIGNALS屏蔽信号后执行pthread_create_impl;_mktls()分配 TLS 内存,calloc创建PosixThread对象;- 若无属性则
pthread_attr_init取默认值; - 分配栈:调用者可用
pthread_attr_setstack自备栈(OpenBSD 下还需FixupCustomStackOnOpenbsd校验),否则由cosmo_stack_alloc()分配并标记PT_OWNSTACK; - 可选分配信号备用栈(
pthread_attr_setsigaltstack_np); - 按
PTHREAD_CREATE_JOINABLE/PTHREAD_CREATE_DETACHED初始化状态,注册进全局线程链表; - 越过「线程卢比孔河」:
__isthreaded = 2,此后运行时锁全面激活; - 调用
__clone()启动PosixThread入口。
源码注释给出了线程的 OSI 模型:
┌──────────────────┐ │ pthread_create() │ - Standard └─────────┬────────┘ Abstraction ┌─────────┴────────┐ │ clone() │ - Polyfill └─────────┬────────┘ ┌────────┬──┴┬─┬─┬─────────┐ - Kernel ┌─────┴─────┐ │ │ │┌┴──────┐ │ Interfaces │ sys_clone │ │ │ ││ tfork │ ┌┴─────────────┐ └───────────┘ │ │ │└───────┘ │ CreateThread │ ┌───────────────┴──┐│┌┴────────┐ └──────────────┘ │ bsdthread_create │││ thr_new │ └──────────────────┘│└─────────┘ ┌───────┴──────┐ │ _lwp_create │ └──────────────┘在 Linux 上,__clone最终使用sys_clone,标志位为CLONE_VM | CLONE_THREAD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_SYSVSEM | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID——共享地址空间、文件系统、打开文件与信号处理,并借助CLONE_CHILD_CLEARTID让内核在线程退出时自动把tib_ctid清为零,这正是pthread_join用 futex 等待的终止信号。其他平台分别落到 FreeBSD 的bsdthread_create、NetBSD 的thr_new(tfork)、OpenBSD 的_lwp_create、XNU 与 Windows 各自的CreateThread/__pthread_create路径。
PosixThread入口框架还完成了几件关键初始化:Linux 下注册rseq(支撑sched_getcpu());若属性为PTHREAD_EXPLICIT_SCHED则调用_pthread_reschedule设置调度参数;安装信号备用栈;用__builtin_setjmp记录跳转点,使pthread_exit能回到此处;恢复创建者传入的信号掩码(Windows/Metal 走 TLS 原子写,其余平台走sys_sigprocmask);随后调用用户入口pt->pt_start(pt->pt_val),最终以pthread_exit(ret)收尾,保证清理钩子必然弹出。
错误语义同样遵循 POSIX:资源不足返回EAGAIN,属性非法返回EINVAL,调度策略无权限返回EPERM,clone的ENOMEM也会被归一化为EAGAIN。
线程属性:默认值与配置项
属性由 thread.h 的pthread_attr_t承载。pthread_attr_init(见 pthread_attr_init.c)只做一件事:用运行时的默认栈大小GetStackSize()与默认守护页大小GetGuardSize()初始化__stacksize/__guardsize,其余字段保持零值(即加入态、继承调度、系统级竞争域)。
关键常量定义于 thread.h:
| 类别 | 常量 | 值 | 说明 |
|---|---|---|---|
| 资源上限 | PTHREAD_KEYS_MAX | 128 | 线程特有数据键上限 |
| 资源下限 | PTHREAD_STACK_MIN | 32768 | 最小栈 32 KiB |
| 析构 | PTHREAD_DESTRUCTOR_ITERATIONS | 4 | TLS 析构函数最大轮数 |
| 互斥类型 | PTHREAD_MUTEX_DEFAULT/NORMAL | 0 / 1 | 普通互斥量 |
| 互斥类型 | PTHREAD_MUTEX_RECURSIVE | 2 | 可重入互斥量 |
| 互斥类型 | PTHREAD_MUTEX_ERRORCHECK | 3 | 检错互斥量 |
| 健壮性 | PTHREAD_MUTEX_STALLED/ROBUST | 0 / 2048 | 持锁线程死亡后的行为 |
| 共享域 | PTHREAD_PROCESS_PRIVATE/SHARED | 0 / 4 | 进程内/跨进程共享 |
| 分离态 | PTHREAD_CREATE_JOINABLE/DETACHED | 0 / 1 | 可汇合/分离 |
| 调度继承 | PTHREAD_INHERIT_SCHED/EXPLICIT_SCHED | 0 / 1 | 继承/显式调度 |
| 取消 | PTHREAD_CANCEL_ENABLE/DISABLE/MASKED | 0 / 1 / 2 | 使能/禁用/掩蔽 |
| 取消类型 | PTHREAD_CANCEL_DEFERRED/ASYNCHRONOUS | 0 / 1 | 延迟/异步取消 |
| 竞争域 | PTHREAD_SCOPE_SYSTEM/PROCESS | 0 / 1 | 系统级/进程级 |
对应的一整套pthread_attr_set*/get*函数覆盖:detachstate、guardsize、inheritsched、schedparam、schedpolicy、scope、stack、stacksize,以及两个 GNU 扩展——信号掩码(setsigmask_np/getsigmask_np,属性含PTHREAD_ATTR_NO_SIGMASK_NP哨兵值)和信号备用栈(setsigaltstack_np/setsigaltstacksize_np)。
汇合与退出:futex 之上的等待语义
pthread_join(见 pthread_join.c)本质上是pthread_timedjoin_np(thread, value_ptr, 0)的特例。真正的等待逻辑在 pthread_timedjoin_np.c:
- 先做完整性断言:目标必须是
Joinable或Terminated状态(对分离线程 join 是未定义行为,直接unassert); _pthread_wait()轮询tib_ctid:Linux、FreeBSD、OpenBSD、Windows 上经cosmo_futex_wait用 futex 挂起;其余平台退化为指数退避轮询;- 自汇合被 POSIX.1-2017 明确要求报告
EDEADLK,源码直接引用了标准原文("it is recommended that the function should fail and report an [EDEADLK] error")并据此实现; - 等待期间是取消点:掩蔽模式下取消返回
ECANCELED(标准要求"the target thread shall not be detached"); - 带
abstime的定时汇合在超时时返回EBUSY; - 成功汇合后,若线程仍被引用则置为
Zombie移入链表尾部待回收,否则直接_pthread_free()释放。
配套的 GNU 扩展还包括pthread_tryjoin_np(不阻塞尝试汇合)与pthread_timedjoin_np,声明均见 thread2.h。pthread_exit通过pt_exiter的 setjmp 缓冲跳回入口框架完成收尾;线程返回值与PTHREAD_CANCELED(((void *)-1))经pt_val传给 join 方。
互斥锁:nsync 驱动的类型化锁
互斥量类型定义在 thread.h 的pthread_mutex_t:_word(类型/所有者/深度编码的原子字)、_futex、_pid,以及一对_nsync[2]与函数指针_lock/_unlock——这是为了按需「升级」到 nsync 实现而预留的。PTHREAD_MUTEX_INITIALIZER即{0, PTHREAD_MUTEX_DEFAULT},另有NORMAL/SHARED/RECURSIVE/ERRORCHECK四种专用初始化器。
底层加锁实现在 mtx_lock.c:非递归互斥量直接委托给nsync_mu_clocklock()(以CLOCK_REALTIME为时钟);递归互斥量则先检查MUTEX_OWNER是否为本线程(线程 ID 来自 TLS 的tib_ptid),是则对MUTEX_DEPTH计数递增(达到MUTEX_DEPTH_MAX报错),否则进入 nsync 等待。pthread_mutex_timedlock、C11 的mtx_timedlock均复用同一路径,超时返回thrd_timedout。文档注释明确:加锁不是取消点、不被信号中断、不可在信号处理器中调用、无优先级继承。健壮互斥量由pthread_mutex_consistent/pthread_mutex_robust相关实现配套处理。
条件变量、屏障与一次性初始化
条件变量pthread_cond_t的字段包括_pshared、_clock(时钟选择)、_waited、_sequence与_waiters原子字。pthread_cond_wait(见 pthread_cond_wait.c)要求调用者持有互斥量,内部转发到pthread_cond_timedwait,等待期间原子地释放互斥量并挂起,被signal/broadcast唤醒后重新获取锁;超时/取消返回对应 errno,PTHREAD_MUTEX_ERRORCHECK下未持锁调用会返回EPERM。pthread_cond_timedwait内部的再入锁(pthread_mutex_lock(wait->mutex))保证唤醒与重锁之间不丢事件。
屏障pthread_barrier_t由_entered/_exited/_round三个原子计数与_count构成,pthread_barrier_wait在最后一组线程到达时放行,并让恰好一个线程得到PTHREAD_BARRIER_SERIAL_THREAD(-1)返回值。一次性初始化pthread_once_t是单原子字结构,PTHREAD_ONCE_INIT为{0},pthread_once实现于 pthread_once.cc。
C11 线程接口与 TLS
除 POSIX 线程外,同目录还提供 C11<threads.h>接口(threads.h):thrd_create/thrd_join/thrd_detach/thrd_sleep、mtx_init/mtx_lock/mtx_timedlock、cnd_*、tss_*等,thrd_*与pthread_*底层共享PosixThread基础设施(例如thrd_create包装pthread_create,mtx_lock与pthread_mutex_lock同源)。
TLS 支持集中于 tls.h 与mktls.c:每个线程一个CosmoTib,承载tib_pthread、tib_ptid/tib_tid、tib_sigmask、tib_ctid(futex 等待地址)、tib_nsync(缓存的 waiter 对象)、tib_malloc(线程私有堆)与tib_keys_*(线程特有数据)。PTHREAD_KEYS_MAX(128)与PTHREAD_DESTRUCTOR_ITERATIONS(4)共同约束 key 表与析构轮数。此外 thread.h 提供pthread_cleanup_push/pthread_cleanup_pop宏:C 模式下借助 GCC 的__attribute__((__cleanup__))在栈展开时自动调用__pthread_cleanup_unwind,C++ 模式则通过 RAII 析构弹出,保证取消/异常路径下资源必然释放。
信号、取消与调度扩展
线程子系统完整覆盖信号交互:pthread_sigmask、每线程信号掩码继承(新建线程从创建者拷贝,见pthread_create中的pt_attr.__sigmask处理)、pthread_kill、以及sigaction中SIGTHR的线程化投递。取消机制提供pthread_cancel、pthread_setcancelstate(含PTHREAD_CANCEL_MASKED掩蔽态)、pthread_setcanceltype、pthread_testcancel与pthread_testcancel_np,取消点在 join/condwait 等阻塞调用中显式声明(BEGIN_CANCELATION_POINT宏)。
调度扩展包括pthread_setschedparam/pthread_getschedparam、pthread_setschedprio、pthread_getschedpolicy及_pthread_reschedule;pthread_atfork通过_pthread_onfork_prepare/parent/child三个回调维护 fork 后线程链表的正确性(fork 会把非当前线程置为 Zombie)。GNU/BSD 扩展函数还包括pthread_getname_np/pthread_setname_np、pthread_getunique_np/pthread_getthreadid_np、pthread_getattr_np、pthread_yield/pthread_yield_np、pthread_decimate_np(主动触发僵尸回收)等,全部声明于 thread2.h。
实战示例与测试
仓库提供了可直接参考的用法:
- examples/thread.c 演示
pthread_create+pthread_join的最小流程;examples/greenbean.c、examples/nesemu1.cc、examples/memleak_backtrace.c 展示了互斥量/条件变量在真实程序中的组合运用; - test/posix 目录包含大量线程相关测试(join/timedjoin、mutex 类型、取消、TLS 析构等),是验证上述语义的权威样例;
- 编译链接时只需包含
LIBC_THREAD包(thread.a),其对 nsync、dlmalloc、libunwind 的依赖由 BUILD.mk 自动解析,用户无需手动链接第三方同步库。
小结
Cosmopolitan 的线程库以 IEEE Std 1003.1-2017 为行为基准,通过struct PosixThread+ 全局链表 + futex/nsync 的组合,把「标准 pthread 语义」与「多平台原生线程原语」统一在同一条调用链上:pthread_create之上是标准 API,之下是 clone polyfill 与各内核接口。理解 posixthread.internal.h 的状态机、pthread_create.c 的克隆流程与 mtx_lock.c 的 nsync 委托,就把握住了这套跨平台线程库的核心骨架;属性、汇合、取消与 C11 接口则构成了完整的上层使用面。
- 标准库
- 操作系统
- 语言运行时
- 系统编程
【免费下载链接】cosmopolitan
build-once run-anywhere c library
相关推荐
Cosmopolitan Libc:让C/C++实现“一次构建,到处运行”的革命性跨平台解决方案
Cosmopolitan Libc:让C/C++实现“一次构建,到处运行”的革命性跨平台解决方案 你是否还在为C/C++程序的跨平台兼容性问题头疼?不同操作系统
标准库操作系统语言运行时系统编程从0到1:Cosmopolitan Libc快速上手教程,3分钟编译你的第一个跨平台程序
从0到1:Cosmopolitan Libc快速上手教程,3分钟编译你的第一个跨平台程序 Cosmopolitan Libc是一个"一次构建,随处运行"的C语言
标准库操作系统语言运行时系统编程突破平台壁垒:Cosmopolitan线程同步原语的跨系统实现
突破平台壁垒:Cosmopolitan线程同步原语的跨系统实现 在多线程编程中,如何确保不同平台上的线程安全始终是开发者面临的一大挑战。Cosmopolitan
标准库操作系统语言运行时系统编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考