当前位置: 首页 > backend >正文

c++ 互斥锁

为练习c++ 线程同步,做了LeeCode 1114题. 按序打印:

给你一个类:

public class Foo {public void first() { print("first"); }public void second() { print("second"); }public void third() { print("third"); }
}

三个不同的线程 A、B、C 将会共用一个 Foo 实例。

  • 线程 A 将会调用 first() 方法
  • 线程 B 将会调用 second() 方法
  • 线程 C 将会调用 third() 方法

请设计修改程序,以确保 second() 方法在 first() 方法之后被执行,third() 方法在 second() 方法之后被执行。

提示:

  • 尽管输入中的数字似乎暗示了顺序,但是我们并不保证线程在操作系统中的调度顺序。
  • 你看到的输入格式主要是为了确保测试的全面性。

示例 1:

输入:nums = [1,2,3]
输出:"firstsecondthird"
解释:
有三个线程会被异步启动。输入 [1,2,3] 表示线程 A 将会调用 first() 方法,线程 B 将会调用 second() 方法,线程 C 将会调用 third() 方法。正确的输出是 "firstsecondthird"。

示例 2:

输入:nums = [1,3,2]
输出:"firstsecondthird"
解释:
输入 [1,3,2] 表示线程 A 将会调用 first() 方法,线程 B 将会调用 third() 方法,线程 C 将会调用 second() 方法。正确的输出是 "firstsecondthird"。

    提示:

    • nums 是 [1, 2, 3] 的一组排列

    答案&测试代码:

     

    #include <iostream>
    #include "listNode.h"
    #include "solution.h"
    #include <algorithm>
    #include <unordered_set>
    #include <unordered_map>
    #include <map>
    #include <string>
    #include <stdio.h>
    #include <stdlib.h>
    #include "solution3.h"
    #include "dataDefine.h"
    #include "uthash.h"
    #include "IntArrayList.h"
    #include <string.h>
    #include <thread>
    #include <atomic>
    #include "DemoClass.h"
    #include <mutex>
    #include <condition_variable>
    #include <functional>void printFirst() {std::cout << "first";
    }void printSecond() {std::cout << "second";
    }void printThird() {std::cout << "third";
    }void testLeeCode1114_() {class Foo { // 函数内部也可以定义类。private:std::mutex mtx; // 互斥锁std::condition_variable cv; // 条件变量bool isFirstDone;bool isSecondDone;public:Foo() {this->isFirstDone = false;this->isSecondDone = false;}void first(function<void()> printFirst) {{ // 加一对{}是为了限制下面加锁的作用域。离开作用域lock_guard自动立即释放锁// lock_guard构造时立即加锁(如果锁被占用会等待锁释放,一旦锁释放就抢占)。不支持手动释放锁。lock_guard 在析构时会自动释放锁。std::lock_guard<std::mutex> lock(mtx); // printFirst() outputs "first". Do not change or remove this line.printFirst();this->isFirstDone = true;}cv.notify_all(); // 唤醒所有等待锁的线程}void second(function<void()> printSecond) {// 立即加锁,同lock_guard, 但是unique_lock比较灵活,还支持延迟加锁。支持手动加锁、释放锁。需要手动管理。std::unique_lock<std::mutex> lock(mtx);// 判断是否满足执行条件。如果不满足就调用wait函数释放锁,该线程阻塞在这里等待被唤醒; 若满足执行条件则继续下面的代码逻辑。 // 被唤醒会判断是否满足执行条件,且满足条件则获取锁,然后继续下面的代码逻辑。不满足条件则继续等待。cv.wait(lock, [this](){return this->isFirstDone;}); // 这里的第二个参数是一个lambda表达式,表示一个匿名函数,该函数捕获this指针(函数体中用到该指针),没有参数。// printSecond() outputs "second". Do not change or remove this line.printSecond();this->isSecondDone = true;lock.unlock(); // 不要忘记释放锁。因为线程被唤醒后需要获取锁资源才会执行到这里,所以必须再释放,不能因为上面wait函数释放锁了就不调用释放了。cv.notify_all(); // 需要通知其他线程, 这里也可以调用notify_one, 只有一个线程在等待了。}void third(function<void()> printThird) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [this]() {return this->isSecondDone;});// printThird() outputs "third". Do not change or remove this line.printThird();lock.unlock();}};Foo foo; // 主线程生成Foo对象。// 因为线程要执行对象里的成员函数,所以第一个参数是函数指针, 第二个参数是该对象,后面的参数是该成员函数的传参,这里传递的函数std::thread t1(&Foo::first, &foo, printFirst);std::thread t3(&Foo::third, &foo, printThird);std::thread t2(&Foo::second, &foo, printSecond);t1.join();t2.join();t3.join();std::cout << endl << "finish" << endl;
    }

    执行结果:

    ok!

    提交到LeeCode:

    ok! 没问题。

    反面教材:

    void testLeeCode1114() { // LeeCode1114.按序打印. 反面教材,主线程加锁,子线程解锁,报错:unlock of unowned mutex 。 class Foo {mutex mtx_1, mtx_2;unique_lock<mutex> lock_1, lock_2;public:Foo() : lock_1(mtx_1, try_to_lock), lock_2(mtx_2, try_to_lock) {}void first(function<void()> printFirst) {printFirst();lock_1.unlock();}void second(function<void()> printSecond) {lock_guard<mutex> guard(mtx_1);printSecond();lock_2.unlock();}void third(function<void()> printThird) {lock_guard<mutex> guard(mtx_2);printThird();}};Foo foo; // 主线程生成Foo对象。std::thread t1(&Foo::first, &foo, printFirst); // 因为是线程要执行对象里的成员函数,所以第一个参数是函数指针, 第二个参数是该对象,后面的参数是该成员函数的传参,这里传递的函数std::thread t3(&Foo::third, &foo, printThird);std::thread t2(&Foo::second, &foo, printSecond);// 主线程等这3个线程执行结束:t1.join();t2.join();t3.join();// 报错: unlock of unowned mutex 
    }

     报错:unlock of unowned mutex 。 

    问题就在于主线程加锁, 然后子线程解锁。所以报错。线程必须先占有锁资源才能解锁。

    总结: 互斥锁就类比一个单人用的卫生间。一个人(线程)进去了会把卫生间锁住(加锁), 此时其他人(线程)想进去只能等待锁释放。

    如果一个人进去卫生间后发现不满足办事条件,比如没带纸(如示例代码中判断不满足执行条件),此时出去等待(如示例代码的wait函数释放锁),别人进去卫生间完事后出来通知说他用完了,然后刚才出去等待的那个人再次竞争到卫生间进去了, 然后会再次检查条件是否满足(如代码中的条件判断),发现卫生间有纸了,ok可以方便了。

    http://www.xdnf.cn/news/1389.html

    相关文章:

  • react的ant-design-pro框架左侧菜单修改为动态路由
  • 【HFP】蓝牙HFP协议来电处理机制解析
  • 手机端touch实现DOM拖拉功能
  • C# 全局 Mutex 是否需使用 `Global\` 前缀
  • 硬核科普丨2025年安全、高效网络准入控制系统深度解析
  • Python flask入门
  • 代码随想录训练营第39天 || 198. 打家劫舍 213. 打家劫舍 II 337. 打家劫舍 III
  • Prometheus 系统化学习计划-总纲
  • Spring Boot 中处理 JSON 数值溢出问题:从报错到优雅解决
  • 商汤绝影生成式AI汽车新品亮相上海车展,引领AI汽车创新潮流
  • gem5教程第三章 向配置脚本添加缓存
  • 数据结构的学习(1)二分查找,利用二分查找找局部最小值,选择排序,冒泡排序,插入排序,位运算的基础知识
  • 我们为什么要选用微服务架构
  • 云计算领域需掌握的核心技术
  • 4.RabbitMQ - 延迟消息
  • 基于自主大型语言模型代理的AIoT智能家居
  • 活动邀请 | 「招商大脑 · 运营引擎」解码大数据招商与智慧园区运营新模式 / 限时报名中!
  • nginx实现同一个端口监听多个服务
  • 通过Quartus II实现Nios II编程
  • 【25软考网工】第三章(2)以太网帧结构与封装、以太网物理层标准
  • JavaScript性能优化
  • 【Linux】详细介绍进程的概念
  • Unity 将Excel表格中的数据导入到Mysql数据表中
  • C++ 中 std::queue的使用详解
  • 线程封装
  • Pytorch图像数据转为Tensor张量
  • 初识Redis · 事务
  • HTTP:十一.HTTP认证概述
  • 12-DevOps-Gitlab托管Jenkinsfile
  • 使用 Conda 创建新环境