云计算百科
云计算领域专业知识百科平台

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

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » c++ 互斥锁
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!