Linux 线程同步核心机制与实战案例总结
一、线程同步机制选择核心原则
场景需求 推荐机制 核心优势 典型案例场景 通用互斥访问 互斥锁(Mutex) 阻塞式加锁,确保原子性,适用于大多数场景 共享变量、文件操作、链表修改 读多写少场景 读写锁(Read-Write Lock) 允许多读单写,提升读并发效率 配置文件读取、缓存数据访问 短临界区(纳秒级) 自旋锁(Spinlock) 非阻塞自旋等待,避免上下文切换开销 高频小数据操作(如计数器自增) 事件通知(条件触发) 条件变量(Condition Var) 配合互斥锁实现线程间等待/唤醒,避免忙等待 生产者-消费者模型、异步任务通知 资源计数/并发控制 信号量(Semaphore) 支持计数控制(如限制最大并发线程数),可跨进程 连接池、任务队列并发处理 多线程阶段同步 屏障(Barrier) 确保所有线程到达集合点后再执行 并行计算多阶段同步(如初始化+计算) 无锁编程(单变量操作) 原子操作(Atomic Op) 无锁高效,保证单个变量操作的原子性 计数器统计、标志位状态切换 异步事件通知(I/O 兼容) eventfd 文件描述符形式,支持 epoll 等多路复用,用户态高效通知 异步框架、网络事件驱动
二、核心机制配合使用场景
1. 条件变量 + 互斥锁:事件驱动同步
场景 :生产者-消费者模型,消费者等待生产者数据就绪。
配合逻辑 :
互斥锁保护共享缓冲区(临界区)。 条件变量用于消费者阻塞等待(缓冲区非空)和生产者通知(缓冲区有数据)。
实测案例 :
# include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int buffer[ 10 ] , count = 0 ;
void * producer ( void * arg) {
for ( int i = 0 ; i < 100 ; i++ ) {
pthread_mutex_lock ( & mutex) ;
buffer[ count++ ] = i;
pthread_cond_signal ( & cond) ;
pthread_mutex_unlock ( & mutex) ;
}
return NULL ;
}
void * consumer ( void * arg) {
for ( int i = 0 ; i < 100 ; i++ ) {
pthread_mutex_lock ( & mutex) ;
while ( count == 0 ) {
pthread_cond_wait ( & cond, & mutex) ;
}
int data = buffer[ -- count] ;
pthread_mutex_unlock ( & mutex) ;
}
return NULL ;
}
2. 读写锁 + 互斥锁:读多写少优化
场景 :配置文件读取(读操作远多于写操作)。
配合逻辑 :
读线程加读锁(共享锁),允许并发读取。 写线程加写锁(独占锁),阻塞所有读/写操作。
实测案例 :
# include <pthread.h>
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int config_data = 0 ;
void * reader ( void * arg) {
for ( int i = 0 ; i < 1000 ; i++ ) {
pthread_rwlock_rdlock ( & rwlock) ;
int data = config_data;
pthread_rwlock_unlock ( & rwlock) ;
}
return NULL ;
}
void * writer ( void * arg) {
for ( int i = 0 ; i < 10 ; i++ ) {
pthread_rwlock_wrlock ( & rwlock) ;
config_data++ ;
pthread_rwlock_unlock ( & rwlock) ;
}
return NULL ;
}
3. 信号量 + 互斥锁:资源计数与互斥
场景 :限制并发访问数据库连接池(最多 5 个线程同时获取连接)。
配合逻辑 :
信号量控制并发数(初始值为 5),线程获取信号量后才能访问连接池。 互斥锁保护连接池本身的操作(如连接的添加/删除)。
实测案例 :
# include <semaphore.h>
# include <pthread.h>
sem_t sem;
pthread_mutex_t pool_mutex;
int connection_pool[ 5 ] ;
void init_pool ( ) {
sem_init ( & sem, 0 , 5 ) ;
pthread_mutex_init ( & pool_mutex, NULL ) ;
}
void * worker ( void * arg) {
sem_wait ( & sem) ;
pthread_mutex_lock ( & pool_mutex) ;
pthread_mutex_unlock ( & pool_mutex) ;
sem_post ( & sem) ;
return NULL ;
}
4. 原子操作 + 内存屏障:无锁计数器(高性能统计)
场景 :统计高频操作次数(如网络请求计数),要求无锁高效。
配合逻辑 :
原子操作(__sync_add_and_fetch
)保证自增的原子性。 内存屏障(__sync_synchronize
)确保多处理器间的内存可见性。
实测案例 :
# include <pthread.h>
volatile int counter = 0 ;
void * thread_func ( void * arg) {
for ( int i = 0 ; i < 10000 ; i++ ) {
__sync_add_and_fetch ( & counter, 1 ) ;
}
return NULL ;
}
int main ( ) {
pthread_t threads[ 10 ] ;
for ( int i = 0 ; i < 10 ; i++ ) {
pthread_create ( & threads[ i] , NULL , thread_func, NULL ) ;
}
for ( int i = 0 ; i < 10 ; i++ ) {
pthread_join ( threads[ i] , NULL ) ;
}
printf ( "Total: %d\n" , counter) ;
return 0 ;
}
5. 屏障 + 互斥锁:多阶段并行计算同步
场景 :并行计算中,所有线程完成初始化后再进入计算阶段。
配合逻辑 :
屏障确保所有线程到达初始化完成点后再继续。 互斥锁保护共享计算结果(如有)。
实测案例 :
# include <pthread.h>
pthread_barrier_t barrier;
pthread_mutex_t result_mutex;
int result = 0 ;
void * worker ( void * arg) {
int thread_id = * ( int * ) arg;
printf ( "Thread %d 完成初始化\n" , thread_id) ;
pthread_barrier_wait ( & barrier) ;
pthread_mutex_lock ( & result_mutex) ;
result += thread_id;
pthread_mutex_unlock ( & result_mutex) ;
return NULL ;
}
int main ( ) {
const int thread_count = 3 ;
pthread_t threads[ thread_count] ;
int ids[ thread_count] = { 1 , 2 , 3 } ;
pthread_barrier_init ( & barrier, NULL , thread_count) ;
pthread_mutex_init ( & result_mutex, NULL ) ;
for ( int i = 0 ; i < thread_count; i++ ) {
pthread_create ( & threads[ i] , NULL , worker, & ids[ i] ) ;
}
for ( int i = 0 ; i < thread_count; i++ ) {
pthread_join ( threads[ i] , NULL ) ;
}
printf ( "Result: %d\n" , result) ;
return 0 ;
}
三、复杂场景综合应用:高并发日志系统
场景描述:
多个线程并发写入日志文件,要求:
写操作互斥(避免文件指针竞争)。 日志缓冲区满时,生产者线程等待消费者线程写入磁盘。 支持动态调整并发写入线程数(通过信号量限制)。
解决方案:
机制组合 :
互斥锁 :保护日志文件句柄和缓冲区操作。条件变量 :缓冲区满时生产者等待,缓冲区有空余时消费者通知生产者。信号量 :限制同时写入磁盘的线程数(避免磁盘过载)。
核心代码框架 :
# include <pthread.h>
# include <semaphore.h>
pthread_mutex_t log_mutex;
pthread_cond_t buffer_cond;
sem_t disk_sem;
char log_buffer[ 1024 ] ;
int buffer_count = 0 ;
void init_log_system ( ) {
pthread_mutex_init ( & log_mutex, NULL ) ;
pthread_cond_init ( & buffer_cond, NULL ) ;
sem_init ( & disk_sem, 0 , 2 ) ;
}
void add_log ( const char * msg) {
pthread_mutex_lock ( & log_mutex) ;
while ( buffer_count >= sizeof ( log_buffer) ) {
pthread_cond_wait ( & buffer_cond, & log_mutex) ;
}
strcat ( log_buffer, msg) ;
buffer_count += strlen ( msg) ;
pthread_cond_signal ( & buffer_cond) ;
pthread_mutex_unlock ( & log_mutex) ;
}
void * log_consumer ( void * arg) {
while ( 1 ) {
pthread_mutex_lock ( & log_mutex) ;
while ( buffer_count == 0 ) {
pthread_cond_wait ( & buffer_cond, & log_mutex) ;
}
char * data = log_buffer;
int len = buffer_count;
buffer_count = 0 ;
pthread_cond_broadcast ( & buffer_cond) ;
pthread_mutex_unlock ( & log_mutex) ;
sem_wait ( & disk_sem) ;
write_to_disk ( data, len) ;
sem_post ( & disk_sem) ;
}
return NULL ;
}
四、关键机制对比与避坑指南
1. 性能对比(锁竞争激烈程度)
机制 上下文切换 忙等待 适用临界区长度 读并发支持 互斥锁 有 无 中长 不支持 读写锁 有 无 中长 支持 自旋锁 无 有 极短(<100ns) 不支持 条件变量 有 无 任意 依赖锁
2. 常见错误与解决方案
问题 原因 解决方案 死锁 嵌套锁顺序不一致、锁未释放 固定加锁顺序、使用 pthread_mutex_trylock
虚假唤醒 条件变量未用 while
循环检查 强制使用 while
而非 if
检查条件 信号量泄漏 sem_wait
后异常退出未调用 sem_post
使用 RAII 或异常处理保证配对调用 自旋锁性能低下 临界区过长导致自旋时间过长 拆分临界区,改用互斥锁
五、最佳实践总结
场景优先 :
简单互斥用互斥锁,读多写少用读写锁,高频短操作自旋锁,事件通知条件变量,资源计数信号量。
最小化同步范围 :
将非必要操作移到临界区外(如日志打印、复杂计算),减少锁持有时间。
无锁化优先 :
单变量操作(如计数器)优先用原子操作,避免锁开销。
工具辅助调试 :
使用 pthread_mutexattr_settype
设置锁类型(如递归锁),通过 strace
监控系统调用次数,perf
定位锁竞争热点。
通过合理组合同步机制,既能保证线程安全,又能最大化并发性能。实际开发中需结合具体场景,在安全性、效率和代码复杂度之间找到平衡。