引入
在 C++ 中,互斥锁(Mutex,Mutual Exclusion)是一种用于保护共享资源的同步机制,能够防止多个线程同时访问同一资源造成冲突或不一致。C++11 引入了线程库,包括相关的互斥锁功能,方便开发者在多线程程序中实现线程安全操作。
1. 互斥锁的基本概念
互斥锁的基本目的是确保在同一时间内只有一个线程可以访问特定的共享资源。锁的状态有两种:已锁定(locked)和未锁定(unlocked)。当一个线程请求锁时,如果锁被其他线程锁定,那么该请求线程会被阻塞,直到锁被释放。
1.1 互斥锁的工作原理
- 加锁:当线程访问共享资源时,它需要先请求获取互斥锁。如果锁可用,互斥锁的状态变为已锁定。
- 访问:获取到锁的线程可以安全地访问共享资源。
- 解锁:使用完共享资源后,线程需释放锁,恢复锁的状态为未锁定,以允许其他线程访问。
2. C++ 中的互斥锁实现
C++11 及以后的版本提供了 <mutex>
头文件,包含了多种互斥锁实现,最常用的有 std::mutex
和 std::recursive_mutex
。
2.1 std::mutex
std::mutex
是基本的互斥锁,用于保护共享资源。
示例:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; // 定义互斥锁
int sharedResource = 0;
void increaseResource() {
// 加锁
mtx.lock();
++sharedResource;
std::cout << "Increased Resource: " << sharedResource << std::endl;
// 解锁
mtx.unlock();
}
int main() {
std::thread t1(increaseResource);
std::thread t2(increaseResource);
t1.join();
t2.join();
return 0;
}
2.2 std::recursive_mutex
std::recursive_mutex
允许同一线程多次加锁而不发生死锁,也就是说,支持同一线程对互斥锁的多重加锁。当一个线程已经持有该互斥锁时,在访问共享资源时可以再次请求获取该锁。
示例:
#include <iostream>
#include <thread>
#include <mutex>
std::recursive_mutex rmtx; // 定义递归互斥锁
int sharedValue = 0;
void addValue(int value) {
rmtx.lock();
sharedValue += value;
std::cout << "Value added: " << sharedValue << std::endl;
if (sharedValue < 5) {
addValue(1); // 再次调用,递归加锁
}
rmtx.unlock();
}
int main() {
std::thread t1(addValue, 1);
t1.join();
return 0;
}
2.3 std::unique_lock
和 std::lock_guard
为了更安全地管理互斥锁,C++11 引入了 std::lock_guard
和 std::unique_lock
。这两种对象是 RAII(Resource Acquisition Is Initialization)技术的应用,会在作用域结束时自动释放互斥锁。
2.3.1 std::lock_guard
std::lock_guard
是一个简单的封装,确保互斥锁在创建时加锁,在退出作用域时自动解锁。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int sharedResource = 0;
void safeIncreaseResource() {
std::lock_guard<std::mutex> lock(mtx); // 自动加锁
++sharedResource;
std::cout << "Safely Increased Resource: " << sharedResource << std::endl;
}
int main() {
std::thread t1(safeIncreaseResource);
std::thread t2(safeIncreaseResource);
t1.join();
t2.join();
return 0;
}
2.3.2 std::unique_lock
std::unique_lock
是更灵活的锁管理器,提供更高级的功能,如可以手动解锁和重新加锁。
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::mutex mtx;
void work() {
std::unique_lock<std::mutex> lock(mtx);
std::cout << "Thread " << std::this_thread::get_id() << " is working." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟工作
// lock 会在作用域结束时自动释放
}
int main() {
std::thread t1(work);
std::thread t2(work);
t1.join();
t2.join();
return 0;
}
3. 互斥锁的注意事项
3.1 死锁
死锁是多线程编程中的常见问题,发生在两个或多个线程互相等待对方释放锁时。在使用互斥锁时,必须小心设计锁的获取顺序,以避免死锁。
3.2 饥饿
饥饿是指某个线程无法获得所需的资源,导致线程长时间不能运行。应确保公平地管理锁的请求,尽可能避免导致线程饥饿的情况。
3.3 性能影响
使用互斥锁会引入一定的性能开销,尤其是在高并发的场景中,锁的管理可能导致性能下降。因此,在设计多线程程序时,应考虑互斥锁的使用频率和场景,尽可能减少临界区的大小,避免长期持有锁。
4. 互斥锁的总结
互斥锁是 C++ 多线程编程中不可或缺的工具,用于保护共享资源并确保数据一致性。通过正确使用 std::mutex
、std::recursive_mutex
、std::lock_guard
和 std::unique_lock
,可以简化并安全地管理多线程中的资源访问。理解互斥锁的工作原理及潜在问题(如死锁和饥饿)对于设计高效且可靠的多线程应用至关重要。
没有回复内容