Skip to content

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 可以显著提升并发度。

系统学习 C++ 生态,深入底层架构