什么是线程同步?
想象一下超市收银台:如果所有顾客(线程)同时挤向同一个收银台(共享资源),场面会一片混乱。线程同步就是给顾客们发"排队号码牌",确保:
- 1. 有序访问:每次只处理一个顾客
- 2. 协调工作:收银员(CPU)高效服务
- 3. 避免冲突:防止算错账(数据错误)
Linux提供5种"排队机制"解决多线程协作问题:
一、互斥锁(单人洗手间规则)
#include <pthread.h>
// 创建锁(相当于洗手间的"有人/无人"标识)
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void* thread_task(void* arg) {
// 尝试进入(如果里面有人就排队等待)
pthread_mutex_lock(&lock);
/* 临界区开始(你的"私人时间") */
printf("Thread %d using resource\n", *(int*)arg);
sleep(1); // 模拟耗时操作
/* 临界区结束 */
// 开门出来(让下一位使用)
pthread_mutex_unlock(&lock);
return NULL;
}
适用场景:
- 文件写入操作
- 银行账户余额修改
- 任何需要"独享"资源的场景
特点:
- ✅ 简单易用
- ⚠️ 过度使用会降低并发性能
二、条件变量(咖啡厅取餐系统)
pthread_cond_t order_ready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t counter_lock = PTHREAD_MUTEX_INITIALIZER;
int order_number = 0;
// 顾客线程(等待取餐)
void* customer(void* arg) {
pthread_mutex_lock(&counter_lock);
while(order_number == 0) { // 必须用while循环检查
pthread_cond_wait(&order_ready, &counter_lock); // 放下锁等待通知
}
printf("Got order %d!\n", order_number);
pthread_mutex_unlock(&counter_lock);
return NULL;
}
// 厨师线程(通知取餐)
void* chef(void* arg) {
sleep(2); // 模拟做饭时间
pthread_mutex_lock(&counter_lock);
order_number = 123;
pthread_cond_signal(&order_ready); // 叫号通知顾客
pthread_mutex_unlock(&counter_lock);
return NULL;
}
工作流程:
- 1. 顾客:锁定柜台 → 检查订单 → 等待叫号
- 2. 厨师:完成订单 → 锁定柜台 → 更新订单 → 发送通知
- 3. 顾客:被唤醒 → 重新检查 → 取餐
适用场景:
- 生产者-消费者模型(如消息队列)
- 线程间任务协调
三、自旋锁(抢车位)
pthread_spinlock_t parking_lock;
// 初始化锁(停车场入口)
pthread_spin_init(&parking_lock, PTHREAD_PROCESS_PRIVATE);
void* driver(void* arg) {
// 开车绕圈找空位(CPU忙等待)
pthread_spin_lock(&parking_lock);
/* 停车成功(临界区) */
printf("Car %d parked\n", *(int*)arg);
// 开走释放车位
pthread_spin_unlock(&parking_lock);
return NULL;
}
适用场景:
- 极短操作(<0.1毫秒)
- 内核开发
- 实时系统
注意事项:
- ⚠️ 会浪费CPU资源
- ✅ 比互斥锁响应更快
四、屏障(旅行团集合点)
#define TOURIST_COUNT 4
pthread_barrier_t meeting_point;
// 初始化屏障(设置集合人数)
pthread_barrier_init(&meeting_point, NULL, TOURIST_COUNT);
void* tourist(void* arg) {
printf("Tourist %d arrived\n", *(int*)arg);
// 等待其他游客
pthread_barrier_wait(&meeting_point);
printf("All here! Start tour\n");
return NULL;
}
执行效果:
Tourist 1 arrived
Tourist 3 arrived
Tourist 2 arrived
Tourist 4 arrived → 所有线程同时继续执行
All here! Start tour
All here! Start tour
All here! Start tour
All here! Start tour
适用场景:
- 并行计算(如矩阵运算)
- 多阶段数据处理
五、读写锁(图书馆规则)
pthread_rwlock_t book_lock = PTHREAD_RWLOCK_INITIALIZER;
// 读者线程(多人同时阅读)
void* reader(void* arg) {
pthread_rwlock_rdlock(&book_lock); // 获取读锁
printf("Reading book...\n");
sleep(1);
pthread_rwlock_unlock(&book_lock);
return NULL;
}
// 作者线程(独占写作)
void* writer(void* arg) {
pthread_rwlock_wrlock(&book_lock); // 获取写锁
printf("Writing new chapter...\n");
sleep(2);
pthread_rwlock_unlock(&book_lock);
return NULL;
}
锁类型对比表:
场景 | 推荐锁类型 | 类比 |
---|---|---|
短时间独占操作 | 自旋锁 | 快速便利店购物 |
长时间独占操作 | 互斥锁 | 餐厅包间用餐 |
多读少写 | 读写锁 | 图书馆 |
线程组协调 | 屏障 | 旅行团集合 |
事件通知 | 条件变量 | 咖啡厅叫号系统 |
新手常见问题解答
Q:该选哪种同步方式?
A:参考这个简单决策流程:
- 1. 需要等待某个事件? → 条件变量
- 2. 所有线程要同时继续? → 屏障
- 3. 读操作远多于写操作? → 读写锁
- 4. 操作时间极短(<0.1ms)→ 自旋锁
- 5. 其他情况 → 互斥锁
Q:锁用太多会怎样?
A:就像超市收银台只开一个窗口:
- 程序变慢(线程排队等待)
- CPU利用率低
- 可能引发死锁(互相等待)
Q:如何避免死锁?
A:遵循三个黄金法则:
- 1. 按固定顺序获取锁(如先A后B)
- 2. 设置锁超时时间
- 3. 使用pthread_mutex_trylock()尝试获取锁