Linux下的多线程编程以POSIX线程(pthread)接口为主,它提供了线程生命周期管理、同步原语和线程间通信的基础设施。理解这些机制的使用边界,比记住API签名更重要。
线程的创建与回收
pthreadcreate创建新线程,入口函数签名为void ()(void *),返回值和参数都通过void *传递。线程结束后可用pthreadjoin阻塞等待并回收资源,或用pthread_detach让线程结束时自动释放。未join也未detach的线程会残留资源,长期运行的服务应避免这种泄漏。
线程属性通过pthreadattrt设置,常见用途是调整栈大小和分离状态。默认栈通常为8MB,线程数量多时应适当调小,但需评估递归深度和局部大数组的占用。
互斥锁与条件变量
共享数据必须受同步原语保护。pthreadmutext是最常用的互斥锁,加锁顺序不一致是死锁的主要来源。实践中应约定全局加锁顺序,或使用pthreadmutextrylock配合超时避免无限等待。
条件变量pthreadcondt用于线程间等待与通知,必须与互斥锁配合使用。等待方在循环中检查条件,防止虚假唤醒:
pthreadmutexlock(&m);
while (!ready) {
pthreadcondwait(&cv, &m);
}
pthreadmutexunlock(&m);
通知方修改共享状态后调用pthreadcondsignal或pthreadcondbroadcast。信号丢失是常见缺陷:如果通知发生在等待之前,等待方会永久阻塞,因此条件判断必须放在持锁环境中。
原子操作与线程局部存储
对于简单计数器,__atomic系列内建函数或C11的stdatomic.h可以避免锁开销。原子操作只保证单个变量的读写一致性,复合逻辑仍需锁保护。
线程局部存储用__thread关键字声明,每个线程拥有独立副本,适合存放错误码、上下文指针等数据,无需加锁即可访问。
常见陷阱与排查
锁粒度过大导致并发度下降,过小则增加竞态风险,应以临界区实际访问的数据范围为准。持有锁时调用可能阻塞的函数(如I/O或再次加锁)容易引发性能问题或死锁。
排查并发问题可借助ThreadSanitizer,编译时加-fsanitize=thread能在运行时报告数据竞争。strace -f和gdb的thread apply all bt可用于定位线程卡死位置。
多线程编程的核心不是API数量,而是对共享状态和同步边界的清晰设计。先明确哪些数据被哪些线程访问,再决定同步策略,代码的可维护性会明显提升。