1. 死锁问题深度解析
死锁是多线程编程中最令人头疼的问题之一,它就像两个固执的人在狭窄的走廊相遇,谁也不肯让路,结果谁都过不去。让我们深入分析这个"交通堵塞"现象。
1.1 死锁的经典场景
想象两个线程(Thread1和Thread2)和两把锁(LockA和LockB):
- Thread1先获取LockA,然后尝试获取LockB
- Thread2先获取LockB,然后尝试获取LockA
当这两个线程的执行时序恰好交错时,就会出现:Thread1持有LockA等待LockB,Thread2持有LockB等待LockA,双方都卡住无法继续执行。这就是典型的死锁场景。
1.2 死锁的四大必要条件
死锁的发生必须同时满足以下四个条件,缺一不可:
- 互斥条件:资源一次只能被一个线程占用(锁的本质特性)
- 请求与保持:线程持有至少一个资源,同时请求其他被占用的资源
- 不剥夺条件:已分配给线程的资源,不能被其他线程强行夺取
- 循环等待:存在一个线程-资源的循环等待链(T1等待T2占用的资源,T2等待T1占用的资源)
1.3 死锁预防实战策略
1.3.1 破坏请求与保持条件
最实用的方法是统一加锁顺序。为所有锁定义一个全局的获取顺序,所有线程都必须按照这个顺序获取锁。例如:
c复制// 定义锁的获取顺序:LockA必须总是在LockB之前获取
void thread_func() {
pthread_mutex_lock(&LockA);
pthread_mutex_lock(&LockB);
// 临界区操作
pthread_mutex_unlock(&LockB);
pthread_mutex_unlock(&LockA);
}
1.3.2 避免锁未释放场景
确保在任何执行路径上(包括异常情况)都能释放已获得的锁。推荐使用RAII模式:
c++复制class LockGuard {
public:
LockGuard(pthread_mutex_t* mutex) : m_mutex(mutex) {
pthread_mutex_lock(m_mutex);
}
~LockGuard() {
pthread_mutex_unlock(m_mutex);
}
private:
pthread_mutex_t* m_mutex;
};
// 使用示例
void safe_operation() {
LockGuard guard(&my_mutex); // 构造函数中加锁
// 临界区操作
// 析构函数中自动解锁,即使抛出异常也会执行
}
1.3.3 设置锁超时机制
使用pthread_mutex_trylock或带超时的锁获取函数,避免无限等待:
c复制int ret = pthread_mutex_trylock(&mutex);
if (ret == EBUSY) {
// 锁被占用,执行备用方案
} else {
// 成功获取锁
// ...操作临界区...
pthread_mutex_unlock(&mutex);
}
注意:在实际项目中,建议使用C++的
std::lock_guard或std::unique_lock等RAII包装类,它们能更好地处理锁的生命周期管理。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 线程同步与条件变量详解
2.1 为什么需要线程同步
考虑一个任务队列场景:
- 消费者线程从队列取任务时,发现队列为空
- 如果没有同步机制,消费者只能不断轮询检查,浪费CPU资源
- 理想情况是:队列为空时消费者休眠,有任务时被唤醒
这就是条件变量的用武之地——它让线程能
