互斥 (Mutual Exclusion)是指在并发编程中,确保多个线程或进程在访问共享资源(如内存、文件或设备)时,不会同时进行操作的机制。通过互斥,可以避免资源竞争和数据不一致的问题。
互斥的核心思想
互斥的核心是在同一时刻只允许一个线程或进程访问共享资源。当一个线程在访问共享资源时,其他线程必须等待,直到该线程完成访问并释放互斥锁。
互斥的实现方式
常见的互斥实现方式包括:
1. 互斥锁(Mutex)
互斥锁是一种同步原语,用于在线程之间保护共享资源。
- 加锁(Locking):在访问共享资源前获取锁。
- 解锁(Unlocking):在访问结束后释放锁。
代码实现:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 互斥锁
int shared_data = 0;
void increment() {
for (int i = 0; i < 100; ++i) {
mtx.lock(); // 加锁
++shared_data;
mtx.unlock(); // 解锁
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Final value of shared_data: " << shared_data << std::endl;
return 0;
}
输出:
Final value of shared_data: 200
如果没有互斥锁,多个线程可能同时修改 shared_data
,导致结果不正确。
2. 读写锁(Reader-Writer Lock)
在一些场景下,读操作不会改变资源的状态,因此可以允许多个线程同时读取,但写操作需要独占资源。读写锁可以提高效率。
- 多个线程可以同时读取资源。
- 只有写线程可以独占访问资源,且写时禁止其他线程读或写。
代码示例:
#include <iostream>
#include <shared_mutex>
#include <thread>
std::shared_mutex rw_lock; // 读写锁
int shared_data = 0;
void reader() {
rw_lock.lock_shared();
std::cout << "Read shared_data: " << shared_data << std::endl;
rw_lock.unlock_shared();
}
void writer() {
rw_lock.lock();
++shared_data;
std::cout << "Write shared_data: " << shared_data << std::endl;
rw_lock.unlock();
}
int main() {
std::thread t1(writer);
std::thread t2(reader);
std::thread t3(reader);
t1.join();
t2.join();
t3.join();
return 0;
}
3. 信号量(Semaphore)
信号量是一种计数器,用于限制同时访问共享资源的线程数量。
- 二进制信号量:类似互斥锁,用于单一访问控制。
- 计数信号量:限制资源的最大并发访问数。
代码示例:
#include <iostream>
#include <thread>
#include <semaphore>
std::counting_semaphore<2> sem(2); // 允许最多两个线程同时访问
int shared_data = 0;
void task(int id) {
sem.acquire(); // 获取信号量
++shared_data;
std::cout << "Thread " << id << " incremented shared_data to " << shared_data << std::endl;
sem.release(); // 释放信号量
}
int main() {
std::thread t1(task, 1);
std::thread t2(task, 2);
std::thread t3(task, 3);
t1.join();
t2.join();
t3.join();
return 0;
}
互斥中的常见问题
- 死锁(Deadlock)
死锁是指多个线程在等待彼此持有的锁,导致程序无法继续运行的现象。
- 产生条件:
- 互斥:资源只能被一个线程占用。
- 请求与保持:一个线程已持有资源,但在等待其他资源时不释放已持有的资源。
- 不可抢占:资源不可强制剥夺。
- 循环等待:线程间形成资源的循环等待关系。
解决方案:
- 避免嵌套锁。
- 遵循固定的锁定顺序。
- 使用死锁检测机制。
- 饥饿(Starvation)
饥饿是指某些线程长期无法获取锁,导致其任务无法执行。
解决方案:
- 使用公平锁(Fair Lock),确保线程按顺序获取锁。
- 优先级反转(Priority Inversion)
优先级反转是指高优先级线程等待低优先级线程释放锁,而低优先级线程又被中优先级线程占用资源的情况。
解决方案
- 使用优先级继承(Priority Inheritance)机制。
总结
互斥是并发编程中保护共享资源的关键机制。通过互斥锁、读写锁、信号量等工具,可以有效地避免资源竞争和数据不一致的问题。