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

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可以方便了。

    相关文章:

  • c++学习流程
  • Python高级爬虫之JS逆向+安卓逆向1.6节: 函数基础
  • 动态规划(一)【背包】
  • 达梦统计信息收集情况检查
  • 便捷的中文转拼音实用工具
  • 关于Agent的简单构建和分享
  • 商汤绝影生成式AI汽车新品亮相上海车展,引领AI汽车创新潮流
  • Java-File类详解(一篇讲透)
  • devops自动化容器化部署
  • 海康NVR配置NAS-TrueNAS
  • NFC 碰一碰实现视频源码,网页与小程序协同
  • TFTP服务调试
  • uv run 都做了什么?
  • 7-1 三种语言的单词转换
  • 【ESP32-IDF笔记】07-ADC 配置和使用
  • 移动端使用keep-alive将页面缓存和滚动缓存具体实现方法 - 详解
  • 程序员思维体操:TDD修炼手册
  • 激光雷达成为新时代「安全气囊」,禾赛推动智能车安全再进化
  • 网络socks 代理
  • 怎么减少tcp 的time_wait时间
  • 小米首次参加上海车展:没有雷军依旧人气爆棚,YU7上市时间未推迟
  • 科普|结石疼痛背后的危机信号:疼痛消失≠警报解除
  • 印控克什米尔发生恐袭事件,外交部:中方反对一切形式的恐怖主义
  • 瞭望:高校大门要向公众打开,不能让“一关了之”成为常态
  • 张又侠董军分别与印尼国防部长会见会谈
  • 耐克领跑女性运动市场:持续加码、创新,更多新增长点有望涌现