当处理多线程并发时,正确使用锁是确保线程安全的关键。
std::mutex
是C++标准库提供的最基本的锁。它的基本使用如下:
#include <iostream>
#include <mutex>
#include <thread>
std::mutex myMutex;
void sharedResourceAccess() {
std::lock_guard<std::mutex> lock(myMutex);
// 访问共享资源的代码
std::cout << "Accessing shared resource...\n";
}
int main() {
std::thread t1(sharedResourceAccess);
std::thread t2(sharedResourceAccess);
t1.join();
t2.join();
return 0;
}
注意事项:
std::lock_guard
是一种简单而安全的方式,它会在作用域结束时自动释放锁。unlock()
,因为忘记释放锁可能导致严重的问题。std::unique_lock
提供了更灵活的锁定和解锁方式,同时支持条件变量。在某些情况下,这种灵活性是很有用的:
#include <iostream>
#include <mutex>
#include <thread>
std::mutex myMutex;
void sharedResourceAccess() {
std::unique_lock<std::mutex> lock(myMutex);
// 访问共享资源的代码
std::cout << "Accessing shared resource...\n";
// lock.unlock(); // 可以手动解锁
}
int main() {
std::thread t1(sharedResourceAccess);
std::thread t2(sharedResourceAccess);
t1.join();
t2.join();
return 0;
}
注意事项:
std::unique_lock
可以在构造时不锁定,也可以手动解锁。std::recursive_mutex
允许同一个线程多次锁定同一把锁。这对于递归函数可能需要在同一线程中多次获取锁的情况很有用:
#include <iostream>
#include <mutex>
#include <thread>
std::recursive_mutex myRecursiveMutex;
void recursiveAccess(int depth) {
std::unique_lock<std::recursive_mutex> lock(myRecursiveMutex);
if (depth > 0) {
recursiveAccess(depth - 1);
}
// 访问共享资源的代码
std::cout << "Accessing shared resource at depth " << depth << "...\n";
}
int main() {
std::thread t1(recursiveAccess, 3);
t1.join();
return 0;
}
注意事项:
std::shared_mutex
是C++14标准引入的互斥锁,它提供了共享/独占两种锁定方式。这使得多个线程可以同时共享资源,而只有一个线程可以独占地修改资源。这在某些情况下能够提高并发性能。
以下是 std::shared_mutex
的主要特点和用法:
共享锁(Shared Lock):
std::shared_lock
来获取。#include <shared_mutex>
std::shared_mutex mySharedMutex;
void readOperation() {
std::shared_lock<std::shared_mutex> lock(mySharedMutex);
// 读取共享资源的代码
}
独占锁(Exclusive Lock):
std::unique_lock
来获取。#include <shared_mutex>
std::shared_mutex mySharedMutex;
void writeOperation() {
std::unique_lock<std::shared_mutex> lock(mySharedMutex);
// 修改共享资源的代码
}
避免写者饥饿(Writer Starvation Avoidance):
std::shared_mutex
的设计旨在避免写者饥饿问题,即允许读者和写者以公平的方式争夺锁。适用于读多写少的场景:
std::shared_mutex
在读多写少的情况下表现得较为优越,因为多个线程可以同时获得共享锁,提高了并发性能。注意事项:
std::shared_lock
进行读取操作,使用 std::unique_lock
进行写入操作。#include <iostream>
#include <shared_mutex>
#include <vector>
#include <thread>
int sharedData=0;
std::shared_mutex mySharedMutex;
void readOperation(int id) {
//std::shared_lock<std::shared_mutex> lock(mySharedMutex);
mySharedMutex.lock_shared();//手动控制
// 读取共享资源的代码
std::cout << "Reader " << id << " reading data: " << sharedData << std::endl;
mySharedMutex.unlock_shared();//手动控制
}
void writeOperation(int id) {
//std::unique_lock<std::shared_mutex> lock(mySharedMutex);
mySharedMutex.lock();//手动控制
// 修改共享资源的代码
sharedData=id;
std::cout << "Writer " << id << " writing data." << std::endl;
mySharedMutex.unlock();//手动控制
}
int main() {
std::vector<std::thread> readers;
std::vector<std::thread> writers;
for (int i = 0; i < 5; ++i) {
readers.emplace_back(readOperation, i);
writers.emplace_back(writeOperation, i);
}
for (auto& reader : readers) {
reader.join();
}
for (auto& writer : writers) {
writer.join();
}
return 0;
}
在这个示例中,读者和写者线程通过 std::shared_mutex
来协调对 sharedData
的读写操作。读者线程使用 std::shared_lock
获得共享锁,而写者线程使用 std::unique_lock
获得独占锁。这样,多个读者可以同时读取,而写者会独占地修改共享资源。
以下是对 std::mutex
,std::unique_lock
,std::recursive_mutex
,和 std::shared_mutex
的特点进行比较的表格:
特点 | std::mutex | std::unique_lock | std::recursive_mutex | std::shared_mutex |
---|---|---|---|---|
类型 | 互斥锁 | 可锁定、可解锁的锁 | 递归互斥锁 | 共享/独占互斥锁 |
RAII 风格 | 有(使用 std::lock_guard ) | 有 | 有 | 有(std::unique_lock ) |
支持条件变量 | 不支持 | 支持 | 不支持 | 支持 |
是否支持递归 | 不支持 | 不支持 | 支持 | 不支持 |
多线程性能 | 适用于大多数场景,较轻量级 | 较为灵活,适用于复杂的场景 | 适用于需要递归锁的场景 | 适用于读多写少的场景 |
锁定粒度 | 整个作用域内的代码 | 可以在较小的范围内进行锁定和解锁 | 整个作用域内的代码 | 可以同时支持独占和共享访问 |
死锁风险 | 高(如果未正确解锁,可能导致死锁) | 低(通过 std::lock() 可以避免死锁) | 递归锁允许同一线程多次获取锁,小心死锁风险 | 低(支持共享和独占访问,适当使用可以减少死锁风险) |
内存开销 | 低(较为轻量级) | 较高(提供了更多的功能) | 较高(需要额外的信息来支持递归锁) | 较高(需要维护更多状态信息) |
这个比较表格总结了这些锁的主要特点,但具体的选择取决于你的应用场景和需求。通常来说,std::mutex
是最基本的锁,而 std::unique_lock
提供了更多的灵活性,特别是在需要支持条件变量的情况下。std::recursive_mutex
对于需要在同一线程中多次获取锁的递归情况很有用。std::shared_mutex
则适用于读多写少的场景,提供了更好的并发性能。