选线程安全库关键看是否隔离共享状态、规避全局/静态数据、并发行为确定;可重入性比线程安全更严格,重入函数必线程安全,反之不成立。

选线程安全库,关键不是看名字里有没有“thread-safe”,而是看它底层是否真正隔离共享状态、是否规避全局/静态数据、是否在并发调用时仍能保持行为确定性。可重入性是更严苛的指标——一个函数若可重入,它天然线程安全;但反过来不成立。
看库函数是否依赖全局或静态状态
很多传统 C 库函数(如 strtok、ctime、gethostbyname)内部使用静态缓冲区,每次调用都会覆盖上次结果。多线程下谁先返回谁“赢”,结果不可预测。这类函数即使加锁调用也难保安全,因为锁无法约束其内部静态变量的读写顺序。
- 优先选用带
_r后缀的重入版本(如 strtok_r、gethostbyname_r),它们把状态通过参数传入,不依赖全局存储 - 避免直接使用 malloc/free —— 它们本身管理全局堆链表,不是可重入的;若必须动态分配,确保调用上下文已做好同步或使用线程本地内存池
- 标准 I/O 函数(如 printf、fgets)虽多数实现加了锁,属于线程安全,但不可重入;信号处理中绝不能调用
关注 STL 和智能指针的实际行为
C++ 标准库容器(std::vector、std::map 等)本身不保证线程安全:多个线程同时读写同一对象会出错;但不同对象间互不影响。例如两个线程分别操作两个独立的 std::shared_ptr,没问题;但若共享同一个 std::shared_ptr 并发调用 reset() 或 operator=,就必须加锁。
- std::shared_ptr 的引用计数操作是原子的(C++11 起),所以多线程增减引用不会崩溃,但对象本身的读写仍需外部保护
- std::unique_ptr 没有引用计数,移动语义是线程安全的,但禁止多线程对同一实例进行 move 或 reset
- 所有 STL 容器的 const 成员函数(如 size()、empty())允许多线程只读访问,无需锁
信号安全与重入场景不能被忽略
线程安全 ≠ 信号安全。一个函数可能在线程环境下加锁后可用,但在信号中断时被重入就会崩。比如你在主线程里用 pthread_mutex_lock 保护一段代码,而信号处理函数里又调用了同样逻辑——此时锁可能已被持有,再 lock 就死锁。
- 信号处理函数中只能调用可重入函数(POSIX 定义的 async-signal-safe 函数,如 write、read、sigprocmask)
- 避免在 signal handler 中调用 printf、malloc、pthread_create 等不可重入函数
- 若业务逻辑复杂,建议用 signalfd 或自管道(self-pipe trick)把信号转为普通 I/O 事件,在主循环中统一处理
验证比文档更可靠
不要轻信“本库线程安全”的宣传。实际验证更有效:
- 查源码或官方文档:是否明确说明“reentrant”或“async-signal-safe”
- 看函数签名:返回值是否指向静态内存?是否隐含全局状态(如 rand() 的内部 seed)?
- 做压力测试:启动多个线程高频调用该函数,观察结果一致性、内存泄漏、core dump
- 用工具检测:如 ThreadSanitizer(TSan)可捕获数据竞争,AddressSanitizer 可发现 use-after-free

















