c++ 多线程(四)

lock_guard、unique_lock

在 C++ 多线程编程中, lock_guardunique_lock 都是用于管理互斥量(mutex)的 RAII 封装类,确保互斥量能被正确释放,避免死锁。

1. lock_guard

基本概念

lock_guard 是一个轻量级的、不可移动复制的 RAII 包装器,它在构造时锁定互斥量,在析构时自动解锁。

常用功能

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>

std::mutex mtx;
int shared_data = 0;

void increment_lock_guard(int n) {
    for (int i = 0; i < n; ++i) {
        std::lock_guard<std::mutex> lock(mtx);  // 构造时自动加锁
        ++shared_data;
        // 离开作用域时自动解锁
    }
}

int main() {
    std::vector<std::thread> threads;
    
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(increment_lock_guard, 1000);
    }
    
    for (auto& t : threads) {
        t.join();
    }
    
    std::cout << "Final value: " << shared_data << std::endl;  // 5000
    return 0;
}

特点

  • 构造即加锁:创建时自动锁定互斥量
  • 析构自动解锁:离开作用域时自动解锁
  • 不可手动操作:不能提前解锁或重新加锁
  • 简单高效:几乎没有额外开销

2. unique_lock

基本概念

unique_lock 是一个功能更丰富的 RAII 包装器,提供了更大的灵活性。

常用功能示例

基本使用
cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void worker_thread() {
    std::unique_lock<std::mutex> lock(mtx);  // 构造时加锁
    
    // 等待条件变量(会自动解锁并重新加锁)
    cv.wait(lock, []{ return ready; });
    
    std::cout << "Worker thread is processing...\n";
    // lock 析构时自动解锁
}

void main_thread() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    
    cv.notify_one();
}
延迟加锁
cpp 复制代码
void delayed_lock_example() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);  // 延迟加锁
    
    // 做一些不需要锁的操作
    std::cout << "Doing some work without lock...\n";
    
    // 需要时手动加锁
    lock.lock();
    // 操作共享数据
    lock.unlock();  // 可以手动解锁
    
    // 还可以重新加锁
    lock.lock();
}
尝试加锁
cpp 复制代码
bool try_lock_example() {
    std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
    
    if (lock.owns_lock()) {
        // 成功获取锁
        std::cout << "Lock acquired successfully\n";
        return true;
    } else {
        // 未获取锁,做其他工作
        std::cout << "Failed to acquire lock, doing alternative work\n";
        return false;
    }
}
带超时的加锁
cpp 复制代码
bool timed_lock_example() {
    std::unique_lock<std::mutex> lock(mtx, std::chrono::milliseconds(100));
    
    if (lock.owns_lock()) {
        std::cout << "Lock acquired within timeout\n";
        return true;
    } else {
        std::cout << "Timeout: failed to acquire lock\n";
        return false;
    }
}

unique_lock 的主要功能特性

功能 描述
lock() 手动加锁
unlock() 手动解锁
try_lock() 尝试加锁,立即返回
try_lock_for() 在一段时间内尝试加锁
try_lock_until() 在指定时间点前尝试加锁
owns_lock() 检查是否持有锁
release() 释放互斥量的所有权但不解锁
mutex() 获取底层互斥量指针

3. 两者的比较

特性 lock_guard unique_lock
手动加锁/解锁 不支持 支持
延迟初始化 不支持 支持
条件变量 不兼容 兼容
尝试加锁 不支持 支持
超时加锁 不支持 支持
性能开销 最小 稍大
所有权转移 不支持 支持移动

4. 使用建议

使用 lock_guard 的情况

cpp 复制代码
// 简单作用域内的锁保护
void simple_function() {
    std::lock_guard<std::mutex> lock(mtx);  // 推荐
    // 操作共享资源
    // 自动解锁
}

使用 unique_lock 的情况

cpp 复制代码
// 需要条件变量的情况
void condition_variable_example() {
    std::unique_lock<std::mutex> lock(mtx);  // 必须使用 unique_lock
    cv.wait(lock, []{ return condition; });
}

// 需要手动控制锁的情况
void manual_control() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
    
    // 复杂的加锁逻辑
    if (some_condition) {
        lock.lock();
        // ...
        lock.unlock();
    }
}

锁的策略参数

cpp 复制代码
// 延迟加锁(构造时不加锁)
std::unique_lock<std::mutex> lock1(mtx, std::defer_lock);

// 尝试加锁(构造时尝试)
std::unique_lock<std::mutex> lock2(mtx, std::try_to_lock);

// 假设已加锁(构造时不加锁)
std::unique_lock<std::mutex> lock3(mtx, std::adopt_lock);

5. 实际示例:线程安全的队列

cpp 复制代码
#include <queue>
#include <mutex>
#include <condition_variable>

template<typename T>
class ThreadSafeQueue {
private:
    std::queue<T> queue_;
    mutable std::mutex mtx_;
    std::condition_variable cv_;
    
public:
    void push(T value) {
        std::lock_guard<std::mutex> lock(mtx_);  // 简单作用域,使用 lock_guard
        queue_.push(std::move(value));
        cv_.notify_one();
    }
    
    bool try_pop(T& value) {
        std::lock_guard<std::mutex> lock(mtx_);
        if (queue_.empty()) {
            return false;
        }
        value = std::move(queue_.front());
        queue_.pop();
        return true;
    }
    
    void wait_and_pop(T& value) {
        std::unique_lock<std::mutex> lock(mtx_);  // 需要条件变量,使用 unique_lock
        cv_.wait(lock, [this]{ return !queue_.empty(); });
        value = std::move(queue_.front());
        queue_.pop();
    }
};

总结

  • lock_guard:简单、高效,适合大多数不需要复杂控制的情况
  • unique_lock:功能丰富、灵活,适合需要条件变量、手动控制或超时机制的情况

在实际开发中,优先考虑使用 lock_guard,只有在需要 unique_lock 的特定功能时才使用它,因为 lock_guard 的性能开销更小。

相关推荐
小尧嵌入式2 小时前
C++模板
开发语言·c++·算法
仰泳的熊猫2 小时前
1120 Friend Numbers
数据结构·c++·算法·pat考试
BestOrNothing_20152 小时前
C++ 成员函数运算符重载深度解析
c++·八股·运算符重载·operator·this指针·const成员函数·const引用
ALex_zry2 小时前
C++中经典的定时器库与实现方式
开发语言·c++
槿花Hibiscus2 小时前
C++基础:session实现和http server类最终组装
服务器·c++·http·muduo
仰泳的熊猫2 小时前
1116 Come on! Let‘s C
数据结构·c++·算法·pat考试
BTU_YC2 小时前
python 内网部署
开发语言·python
千疑千寻~3 小时前
【QML】C++访问QML控件
c++·qml
yyovoll3 小时前
java线程知识点介绍1
java·开发语言