C++ 并发编程中的锁:总结与实践
C++ 并发编程中的锁:总结与实践
在 C++ 并发编程中,我们使用锁来保护共享数据,防止数据竞争。你提到的 std::mutex
和 std::lock_guard
是最基础也是最重要的同步原语。但 C++ 标准库提供了更丰富的锁类型来满足不同的并发场景。
1. 互斥锁 (Mutex)
这些锁用于在临界区内提供互斥访问,一次只允许一个线程进入。
std::mutex
:最简单、最基础的互斥锁。它不能被同一个线程重复锁定,否则会导致死锁。为了避免忘记解锁,我们总是搭配 RAII 类 std::lock_guard
或 std::unique_lock
使用。
std::lock_guard
就像一个房间的钥匙,当一个线程拿到钥匙(构造时加锁)后,其他所有想要进入的线程都必须等待,直到它离开房间(离开作用域时自动解锁)。
// 示例:使用 std::mutex 和 std::lock_guard
#include <iostream>
#include <thread>
#include <mutex>int shared_counter = 0;
std::mutex mtx;void increment_counter() {for (int i = 0; i < 10000; ++i) {std::lock_guard<std::mutex> lock(mtx); // RAII 加锁shared_counter++;} // 离开作用域时自动解锁
}int main() {std::thread t1(increment_counter);std::thread t2(increment_counter);t1.join();t2.join();std::cout << "Final counter value: " << shared_counter << std::endl;return 0;
}
2. 灵活的锁 (Unique Lock)
在某些复杂场景中,lock_guard
的功能可能不够用。std::unique_lock
就像一个可以定制功能的智能钥匙。它提供了延迟加锁、可移动所有权、以及在临界区结束前提前解锁等功能。
以下代码展示了 std::unique_lock
的延迟加锁功能,这在需要同时锁定多个互斥锁以避免死锁时非常有用。
// 示例:使用 std::unique_lock
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx1;
std::mutex mtx2;void complicated_task() {std::unique_lock<std::mutex> lock1(mtx1, std::defer_lock); // 延迟加锁std::unique_lock<std::mutex> lock2(mtx2, std::defer_lock); // 延迟加锁// 使用 std::lock 确保同时加锁,避免死锁std::lock(lock1, lock2);// ... 在此执行临界区任务 ...std::cout << "Locks acquired for complicated task." << std::endl;
}int main() {std::thread t1(complicated_task);std::thread t2(complicated_task);t1.join();t2.join();return 0;
}
std::lock
如何解决死锁?
std::lock
函数被设计用来原子地(atomically)同时锁定多个互斥量。
- 工作原理:
std::lock
会尝试同时获取所有传递给它的互斥锁。如果它成功地获取了所有锁,就会返回。如果它只获取了部分锁,但无法获取剩余的锁,它会自动释放所有已经获取的锁,然后再次尝试,直到所有锁都被成功获取。 - 避免死锁:这种“要么全部获取,要么一个都不获取”的策略,从根本上消除了因获取顺序不同而导致的死锁。无论有多少线程同时调用
std::lock
,它们都会以一种无死锁的算法来争夺这些锁。
3. 读写分离 (Shared Mutex)
想象一下图书馆,允许多人同时进来读书(读操作),但当有人想要整理书架(写操作)时,必须要求所有人离开。
std::shared_mutex
就是这样的锁。它有两把钥匙:一把是独占锁,由 std::unique_lock
管理,只允许一个线程进入写;另一把是共享锁,由 std::shared_lock
管理,允许多个线程同时进入读。这在读操作远多于写操作的场景中,可以极大地提升程序的并发性能。
// 示例:使用 std::shared_mutex
#include <iostream>
#include <unordered_map>
#include <string>
#include <thread>
#include <shared_mutex>
#include <chrono>class ThreadSafeCache {
private:std::unordered_map<int, std::string> cache_;mutable std::shared_mutex mutex_; // 读写锁public:// 读操作:使用 std::shared_lock 获取共享锁void get(int key) const {std::shared_lock<std::shared_mutex> lock(mutex_);if (cache_.count(key)) {std::cout << "Reader thread: Found key " << key << std::endl;}}// 写操作:使用 std::unique_lock 获取独占锁void set(int key, const std::string& value) {std::unique_lock<std::shared_mutex> lock(mutex_);cache_[key] = value;std::cout << "Writer thread: Wrote key " << key << std::endl;}
};void reader_task(ThreadSafeCache& cache) {for (int i = 0; i < 5; ++i) {cache.get(i);std::this_thread::sleep_for(std::chrono::milliseconds(5));}
}void writer_task(ThreadSafeCache& cache) {for (int i = 0; i < 5; ++i) {cache.set(i, "data");std::this_thread::sleep_for(std::chrono::milliseconds(20));}
}int main() {ThreadSafeCache cache;std::thread t1(writer_task, std::ref(cache));std::thread t2(reader_task, std::ref(cache));std::thread t3(reader_task, std::ref(cache));t1.join();t2.join();t3.join();return 0;
}// --- 程序输出(示例,每次运行结果可能不同)---
// Reader 0 read: Value-0
// Reader 2 read: Value-0
// Reader 1 read: Value-0
// Writer wrote: 0 -> Value-0
// Reader 3 read: Value-0
// Writer wrote: 1 -> Value-1
// Reader 0 read: Value-1
// Reader 2 read: Value-1
// Writer wrote: 2 -> Value-2
// Reader 1 read: Value-2
// Reader 3 read: Value-2
// Reader 0 read: Value-2
// Writer wrote: 3 -> Value-3
// Reader 2 read: Value-3
// Reader 1 read: Value-3
// Reader 3 read: Value-3
// Writer wrote: 4 -> Value-4
// Reader 0 read: Value-4
// Reader 2 read: Value-4
// Reader 1 read: Value-4
// Reader 3 read: Value-4