C++11锁机制mutex、lock_guard与unique_lock详细解析

 更新时间:2025年05月26日 09:17:00   作者:xiaokang-coding  
这篇文章主要介绍了C++11锁机制mutex、lock_guard与unique_lock详细解析,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

大家好啊,我是小康。今天咱们聊点"家常"——那些让C++程序员又爱又恨的多线程同步工具!

如果你曾经被多线程搞得头大,或者听到"死锁"就心慌,那这篇文章就是为你准备的。今天我要用最接地气的方式,帮你彻底搞懂C++11中的三兄弟:mutexlock_guardunique_lock

为啥要用这些同步工具?

先别急着学怎么用,咱们得先知道为啥要用啊!

想象一下:你和室友共用一个卫生间。如果你们同时冲进去...嗯,画面太美不敢想象。所以你们会怎么做?肯定是先看看有没有人,没人才进去,然后反锁门,用完了再开门。

多线程程序也一样!不同的线程可能会同时访问同一块"地盘"(共享资源),如果不加控制,就会出现数据错乱、程序崩溃等一系列灾难。

这时候,我们的三兄弟就闪亮登场了!

老大:mutex(互斥锁)

mutex就像那个卫生间的门锁,它是最基础的同步工具,核心功能就两个:锁上(lock)和开锁(unlock)。

来看个最简单的例子:

#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;  // 这就是我们的"门锁"
int shared_value = 0;  // 这是我们要保护的"卫生间"
void increment_value() {
    mtx.lock();  // 进去之前先锁门
    std::cout << "线程 " << std::this_thread::get_id() << " 进入临界区" << std::endl;
    // 想象这是个很复杂的操作,需要一些时间
    shared_value++;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout << "线程 " << std::this_thread::get_id() << " 即将离开,共享值为: " << shared_value << std::endl;
    mtx.unlock();  // 用完了记得开锁,让别人能进来
}
int main() {
    std::thread t1(increment_value);
    std::thread t2(increment_value);
    t1.join();
    t2.join();
    return 0;
}

看着挺简单对吧?但这有个大坑——如果在lockunlock之间发生了异常,或者你单纯忘记了unlock,那么锁就永远不会被释放,其他线程永远进不了"卫生间"!这就是传说中的"死锁"。

正因如此,直接使用mutex很容易出错,所以C++11给我们提供了更智能的解决方案。

老二:lock_guard(保安大哥)

lock_guard就像一个靠谱的保安大哥。当你进"卫生间"时,他会自动锁门;当你出来时,无论是正常出来还是因为突发情况(异常)跑出来,他都会负责解锁。

看看用lock_guard如何改写上面的例子:

void safer_increment() {
    std::lock_guard<std::mutex> guard(mtx);  // 保安上岗,自动锁门
    std::cout << "线程 " << std::this_thread::get_id() << " 进入临界区" << std::endl;
    // 即使这里抛出异常,离开函数作用域时lock_guard也会自动解锁
    shared_value++;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout << "线程 " << std::this_thread::get_id() << " 即将离开,共享值为: " << shared_value << std::endl;
    // 不需要手动解锁,guard离开作用域时会自动解锁
}

是不是简单多了?这就是RAII(资源获取即初始化)的魅力——资源的管理跟对象的生命周期绑定在一起。lock_guard一旦创建就会锁定互斥量,一旦销毁(离开作用域)就会解锁互斥量。

不过lock_guard有个局限性:一旦上锁,在其生命周期内你就不能手动解锁了。就像你请了个特别死板的保安,他坚持要等你彻底离开才会开门,中途想出去透个气都不行。

老三:unique_lock(万能管家)

如果说lock_guard是保安大哥,那unique_lock就是一个高级管家,不但能自动锁门解锁,还能根据你的指令随时锁门或开门,甚至可以"借"钥匙给别人。

来看个例子:

void flexible_operation() {
    std::unique_lock<std::mutex> superlock(mtx);  // 默认情况下构造时会锁定mutex
    std::cout << "线程 " << std::this_thread::get_id() << " 开始工作" << std::endl;
    shared_value++;
    // 假设这里不需要锁了,可以提前解锁
    superlock.unlock();
    std::cout << "临时解锁,执行一些不需要保护的操作" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(50));
    // 需要再次访问共享资源时,可以重新上锁
    superlock.lock();
    shared_value++;
    std::cout << "线程 " << std::this_thread::get_id() << " 完成工作,共享值为: " << shared_value << std::endl;
    // 同样,不需要手动解锁,离开作用域时会自动解锁(如果当时处于锁定状态)
}

除了手动lockunlockunique_lock还有更多高级功能:

std::unique_lock<std::mutex> master_lock(mtx, std::defer_lock);  // 创建时不锁定
if (master_lock.try_lock()) {  // 尝试锁定,如果失败也不会阻塞
    std::cout << "成功获取锁!" << std::endl;
} else {
    std::cout << "获取锁失败,但我可以去做别的事" << std::endl;
}
// 还可以配合条件变量使用
std::condition_variable cv;
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; });  // 这里会自动解锁并等待条件满足

unique_locklock_guard灵活,但也付出了一点性能代价,它内部需要维护更多状态信息。

三兄弟大比拼

说了这么多,来个简单对比:

特性mutexlock_guardunique_lock
手动锁定/解锁
异常安全❌(需手动保证)
条件变量配合
尝试锁定(try_lock)
性能开销最小很小稍大
使用难度容易出错简单安全灵活但复杂

实战:模拟ATM取款与系统维护

最后用一个贴近生活的例子来巩固一下。假设我们有个ATM系统,既要处理用户取款,又要处理银行的系统维护:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
class ATMSystem {
private:
    double cash_available;  // ATM中可用现金
    bool maintenance_mode;  // 是否处于维护模式
    std::mutex mtx;
    std::condition_variable cv;  // 条件变量,用于等待维护结束
public:
    ATMSystem(double initial_cash) : cash_available(initial_cash), maintenance_mode(false) {}
    // 用户取款操作
    bool withdraw(double amount) {
        // 这里必须用unique_lock,因为条件变量wait需要它
        std::unique_lock<std::mutex> lock(mtx);
        // 如果ATM正在维护中,等待维护结束
        cv.wait(lock, [this] { return !maintenance_mode; });
        // 检查余额并取款
        if (cash_available >= amount) {
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
            cash_available -= amount;
            std::cout << "取出: " << amount << ",ATM剩余现金: " << cash_available << std::endl;
            return true;
        }
        std::cout << "ATM现金不足,取款失败!当前剩余: " << cash_available << std::endl;
        return false;
    }
    // 开始系统维护
    void start_maintenance() {
        std::lock_guard<std::mutex> guard(mtx);
        maintenance_mode = true;
        std::cout << "ATM进入维护模式,暂停服务" << std::endl;
    }
    // 结束系统维护
    void end_maintenance() {
        {
            std::lock_guard<std::mutex> guard(mtx);
            maintenance_mode = false;
            std::cout << "ATM维护完成,恢复服务" << std::endl;
        }
        // 通知所有等待的取款线程
        cv.notify_all();
    }
    // 补充现金
    void refill_cash(double amount) {
        std::lock_guard<std::mutex> guard(mtx);
        cash_available += amount;
        std::cout << "ATM补充现金: " << amount << ",当前总现金: " << cash_available << std::endl;
    }
};
// 模拟用户线程
void user_thread(ATMSystem& atm, int user_id) {
    std::cout << "用户 " << user_id << " 尝试取款..." << std::endl;
    atm.withdraw(100);
}
// 模拟维护线程
void maintenance_thread(ATMSystem& atm) {
    std::this_thread::sleep_for(std::chrono::milliseconds(20));
    atm.start_maintenance();
    // 执行维护操作
    std::this_thread::sleep_for(std::chrono::milliseconds(300));
    atm.refill_cash(500);
    // 维护结束
    atm.end_maintenance();
}
int main() {
    ATMSystem atm(300);  // 初始现金300元
    // 启动一个维护线程和多个用户线程
    std::thread maint(maintenance_thread, std::ref(atm));
    std::vector<std::thread> users;
    for (int i = 1; i <= 5; ++i) {
        users.push_back(std::thread(user_thread, std::ref(atm), i));
    }
    // 等待所有线程结束
    maint.join();
    for (auto& t : users) {
        t.join();
    }
    return 0;
}

总结

  • mutex:最基础的锁,需要手动锁定和解锁,用不好容易出问题,就像自己管理卫生间门锁。
  • lock_guard:简单安全的自动锁,构造时锁定,析构时解锁,但不能中途操作锁状态,就像请了个死板但可靠的保安。
  • unique_lock:功能最全面的锁包装器,灵活性最高,但有轻微的性能开销,就像一个万能的管家。

最佳实践

  • 简单场景,优先使用lock_guard
  • 需要条件变量或灵活锁定/解锁时,使用unique_lock
  • 对性能极度敏感的场景,考虑直接使用mutex,但要非常小心

希望这篇文章能让你对C++11的同步工具有个清晰的认识。多线程不再可怕,熟练掌握这"三兄弟",你就能写出安全高效的并发程序啦!

到此这篇关于C++11锁机制三兄弟大比拼:mutex、lock_guard与unique_lock的文章就介绍到这了,更多相关C++ mutex、lock_guard与unique_lock内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:

相关文章

  • C语言实例讲解选择语句的使用

    C语言实例讲解选择语句的使用

    选择语句是C语言中的重要组成部分,该部分也比较基础,本文将通过最通俗易懂的方式来为大家做出讲解。相信通过本文的学习,让大家一举拿下C语言选择语句的知识点
    2022-05-05
  • 浅谈C++的语句语法与强制数据类型转换

    浅谈C++的语句语法与强制数据类型转换

    这篇文章主要介绍了浅谈C++的语句语法与强制数据类型转换,是C++入门学习中的基础知识,需要的朋友可以参考下
    2015-09-09
  • C语言实现字符串相连的四种方法

    C语言实现字符串相连的四种方法

    本文介绍了C语言中四种常见的字符串连接方法:strcat函数直接连接,sprintf函数格式化输出,先复制后连接(strcpy+strcat),以及使用指针操作,详细展示了每个方法的代码示例,需要的朋友可以参考下
    2025-07-07
  • 如何使用递归和非递归方式反转单向链表

    如何使用递归和非递归方式反转单向链表

    以下是对使用递归和非递归方式反转单向链表的示例进行了详细的分析介绍,需要的朋友可以过来参考下
    2013-07-07
  • C/C++编程中const的使用详解

    C/C++编程中const的使用详解

    这篇文章主要为大家详细介绍了C/C++编程中const的使用,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • C语言实现扫雷游戏详细流程

    C语言实现扫雷游戏详细流程

    windows自带的游戏《扫雷》是陪伴了无数人的经典游戏,本文将利用C语言实现这一经典的游戏,文中的示例代码讲解详细,感兴趣的可以学习一下
    2022-05-05
  • Qt GUI图形图像开发之QT表格控件QTableView详细使用方法与实例

    Qt GUI图形图像开发之QT表格控件QTableView详细使用方法与实例

    这篇文章主要介绍了Qt GUI图形图像开发之QT表格控件QTableView详细使用方法与实例,需要的朋友可以参考下
    2020-03-03
  • C语言实现宾馆管理系统课程设计

    C语言实现宾馆管理系统课程设计

    这篇文章主要为大家详细介绍了C语言实现宾馆管理系统课程设计,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • Qt中const QString转换 char *可能的坑

    Qt中const QString转换 char *可能的坑

    本文主要介绍了Qt中const QString转换 char *可能的坑,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • 浅谈C语言转义字符和格式控制符

    浅谈C语言转义字符和格式控制符

    下面小编就为大家带来一篇浅谈C语言转义字符和格式控制符。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-08-08

最新评论