判断函数是否线程安全,关键看是否依赖全局/静态数据、使用非可重入资源或缺乏锁保护;带“_r”后缀的POSIX函数(如strtok_r)是可重入且线程安全的,而man手册中标注“MT-Safe”的函数亦为线程安全。

判断一个函数是否线程安全,关键看它是否依赖或修改全局/静态数据、是否使用了非可重入的资源(如静态缓冲区)、以及是否显式加锁保护共享状态。Linux标准库中,很多传统C函数(尤其是早期POSIX前设计的)默认是非线程安全的,而现代替代版本通常以“_r”(reentrant)为后缀命名。
看函数名后缀:带 _r 的通常是线程安全版本
这是最直观的识别方式。POSIX标准为许多易出问题的函数定义了可重入(reentrant)变体,通过增加 _r 后缀区分:
- gethostbyname()(非线程安全) vs gethostbyname_r()(线程安全)
- strtok()(非线程安全,用静态变量保存上下文) vs strtok_r()(线程安全,由调用者传入上下文指针)
- ctime() vs ctime_r();localtime() vs localtime_r()
注意:_r 版本不是简单加锁封装,而是彻底避免静态状态,把所有中间数据交由调用者管理,这才是真正可重入和线程安全的基础。
查 man 手册:重点关注 “MT-Safe” 和 “MT-Unsafe” 标注
Linux man pages(特别是 man 7 pthreads 或具体函数的 man 3 页面)明确标注了线程安全性:
- 运行
man 3 strtok,在 NOTES 或 THREAD SAFETY 部分会写明 “MT-Unsafe race:strtok” - 运行
man 3 strtok_r,则可能标注 “MT-Safe” 或 “MT-Safe locale” - 常见标记含义:MT-Safe = 线程安全;MT-Unsafe = 非线程安全;MT-Safe locale = 在 locale 不变的前提下线程安全
部分函数(如 malloc、free)虽无 _r 版本,但手册会说明其实现已保证线程安全——因为它们内部使用了原子操作或互斥锁。
警惕隐式共享状态:即使无全局变量,也可能不安全
有些函数看似只操作参数,但仍可能间接引发竞争:
- errno 是每个线程独立的(现代glibc中),所以直接读写 errno 本身是线程安全的;但若函数内部未及时保存 errno 值就调用其他函数,可能导致覆盖
- setlocale() 修改进程级 locale 设置,影响后续所有线程的 locale 敏感函数(如 toupper、strftime),因此应避免在多线程环境中动态调用
- 自定义函数若使用 static 局部变量、全局缓冲区 或调用上述非线程安全函数,即使自身没加锁,也默认非线程安全
实际开发建议:默认选择安全版本,必要时加锁兜底
编写多线程程序时,应主动规避风险:
- 优先使用带 _r 后缀的函数,并严格按文档传入额外的缓冲区和上下文参数
- 对无法替换的非线程安全函数(如某些专有库接口),用 pthread_mutex_t 或 C11 的 mtx_t 加互斥锁保护调用段
- 编译时开启 -D_GNU_SOURCE(尤其使用 _r 函数时),并链接 -lpthread(部分旧系统需要)
- 用 valgrind --tool=helgrind 或 ThreadSanitizer(-fsanitize=thread) 检测潜在竞态










