std::mutex & 锁
C++ 提供了丰富的互斥量和锁工具,从基础的
mutex到读写锁、条件变量,覆盖所有同步场景。
互斥量类型
cpp
#include <mutex>
#include <shared_mutex>
// std::mutex:基础互斥量,不可递归
std::mutex mtx;
// std::recursive_mutex:可递归加锁(同一线程可多次加锁)
std::recursive_mutex rmtx;
// std::timed_mutex:支持超时
std::timed_mutex tmtx;
tmtx.try_lock_for(std::chrono::milliseconds(100));
tmtx.try_lock_until(std::chrono::steady_clock::now() + std::chrono::seconds(1));
// std::shared_mutex(C++17):读写锁
std::shared_mutex rwmtx;
// 写锁(独占)
std::unique_lock write_lock(rwmtx);
// 读锁(共享,多个读者可同时持有)
std::shared_lock read_lock(rwmtx);锁包装器
cpp
#include <mutex>
std::mutex mtx;
// lock_guard:最简单,构造加锁,析构解锁,不可移动
{
std::lock_guard<std::mutex> lock(mtx);
// 临界区
} // 自动解锁
// unique_lock:更灵活,支持延迟加锁、手动解锁、移动
{
std::unique_lock<std::mutex> lock(mtx); // 立即加锁
// 临界区
lock.unlock(); // 手动解锁
// 非临界区操作
lock.lock(); // 重新加锁
}
// 延迟加锁
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
// 做一些不需要锁的工作
lock.lock(); // 手动加锁
// 尝试加锁
std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
if (lock.owns_lock()) {
// 成功获取锁
}
// scoped_lock(C++17):同时锁多个互斥量,避免死锁
std::mutex m1, m2;
std::scoped_lock lock(m1, m2); // 原子地锁定两个互斥量读写锁
cpp
#include <shared_mutex>
#include <unordered_map>
class ThreadSafeCache {
mutable std::shared_mutex rwmtx_;
std::unordered_map<std::string, std::string> cache_;
public:
// 读操作:共享锁(多个线程可同时读)
std::optional<std::string> get(const std::string& key) const {
std::shared_lock lock(rwmtx_);
auto it = cache_.find(key);
if (it != cache_.end()) return it->second;
return std::nullopt;
}
// 写操作:独占锁(只有一个线程可写)
void set(const std::string& key, std::string value) {
std::unique_lock lock(rwmtx_);
cache_[key] = std::move(value);
}
void remove(const std::string& key) {
std::unique_lock lock(rwmtx_);
cache_.erase(key);
}
};条件变量
cpp
#include <condition_variable>
#include <queue>
// 生产者-消费者模式
template<typename T>
class BlockingQueue {
std::queue<T> queue_;
std::mutex mtx_;
std::condition_variable cv_;
size_t max_size_;
bool closed_ = false;
public:
explicit BlockingQueue(size_t max = 100) : max_size_(max) {}
// 生产者:放入数据(队列满时阻塞)
bool push(T item) {
std::unique_lock lock(mtx_);
cv_.wait(lock, [this] {
return queue_.size() < max_size_ || closed_;
});
if (closed_) return false;
queue_.push(std::move(item));
cv_.notify_one(); // 通知消费者
return true;
}
// 消费者:取出数据(队列空时阻塞)
std::optional<T> pop() {
std::unique_lock lock(mtx_);
cv_.wait(lock, [this] {
return !queue_.empty() || closed_;
});
if (queue_.empty()) return std::nullopt;
T item = std::move(queue_.front());
queue_.pop();
cv_.notify_one(); // 通知生产者
return item;
}
void close() {
std::lock_guard lock(mtx_);
closed_ = true;
cv_.notify_all();
}
};
// 使用
BlockingQueue<int> bq(10);
std::thread producer([&bq] {
for (int i = 0; i < 100; ++i) bq.push(i);
bq.close();
});
std::thread consumer([&bq] {
while (auto item = bq.pop()) {
std::cout << *item << "\n";
}
});
producer.join();
consumer.join();避免死锁
cpp
// 死锁场景
std::mutex m1, m2;
void thread1() {
std::lock_guard l1(m1);
std::lock_guard l2(m2); // 如果 thread2 先锁了 m2,死锁!
}
void thread2() {
std::lock_guard l1(m2);
std::lock_guard l2(m1);
}
// 解决 1:固定加锁顺序
void safe_thread1() {
std::lock_guard l1(m1); // 总是先锁 m1
std::lock_guard l2(m2);
}
void safe_thread2() {
std::lock_guard l1(m1); // 总是先锁 m1
std::lock_guard l2(m2);
}
// 解决 2:std::scoped_lock(C++17,推荐)
void safe_both() {
std::scoped_lock lock(m1, m2); // 内部使用死锁避免算法
}
// 解决 3:std::lock + adopt_lock
void safe_adopt() {
std::lock(m1, m2); // 原子地锁定两个
std::lock_guard l1(m1, std::adopt_lock);
std::lock_guard l2(m2, std::adopt_lock);
}性能优化
cpp
// 减小临界区
void bad() {
std::lock_guard lock(mtx);
auto data = fetch_from_db(); // 不需要锁!
process(data); // 不需要锁!
cache_[key] = data; // 只有这里需要锁
}
void good() {
auto data = fetch_from_db(); // 锁外执行
process(data);
std::lock_guard lock(mtx); // 只锁必要的部分
cache_[key] = data;
}
// 读多写少:用 shared_mutex
// 无竞争:用 atomic
// 短临界区:考虑自旋锁
// 长临界区:用 mutex(让出 CPU)关键认知
优先用 scoped_lock 锁多个互斥量(避免死锁),用 lock_guard 锁单个。条件变量的 wait 必须在循环中检查条件(防止虚假唤醒)。读多写少的场景用 shared_mutex 可以显著提升并发度。