V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
LuckyPocketWatch
V2EX  ›  C++

如何保证一个成员函数在另一个成员函数之前获得 mutex?

  •  
  •   LuckyPocketWatch · 7 天前 · 804 次点击

    假设有个类的两个成员函数,需要在不同的线程里执行,这两个成员函数需要写入同一个文件,因此需要使用 mutex 确保这两个函数不会同时写,代码类似这样

    class Widget{
    public:
        void funA();
        void funnB();
    private:
        std::mutex mutex;
    };
    void Widget::funA(){
        {
            std::unique_lock(mutex);
            //operate A;
        }
    }
    void Widget::funB(){
        {
            std::unique_lock(mutex);
            //operate B;
        }
    }
    

    然后设计两个函数来封装这两个成员函数,类似

    void execA(Widget* w){
        w->funcA();
    };
    

    最后程序里,生成一个 Widget 的对象,需要在两个线程里执行这两个函数,即

    Widget* w{};
    std::jthread threadA(execA, &w);  //在一个线程里执行 A 函数
    std::jthread threadB(execB, &w);  //在另一个线程里执行 B 函数
    

    在这种情况下,大多数式成员函数 funcA()先执行,但多次尝试后,有几次式 funcB()先获得 mutex 。

    在下列三个前置条件下

    1. 使用 c++20 标准,并可以使用 vs2022 最新版支持的 c++23 标准
    2. 不该用 std::thread
    3. 不使用 std::this_thread::sleep_for()对 B 进行睡眠

    我向问下,有没有办法确保 A 一定在 B 之前获得 mutex ?

    7 条回复    2024-11-20 09:37:28 +08:00
    LcDraven
        1
    LcDraven  
       7 天前
    用信号量控制两个线程的同步关系
    billlee
        2
    billlee  
       7 天前
    你的需求是 threadB 在 threadA 后执行,这个不是 mutex 能解决的问题。请使用条件变量
    LcDraven
        3
    LcDraven  
       7 天前
    @billlee 他的场景用条件变量不是把简单问题复杂化了吗?用信号量最优吧我觉得,或者再用一个互斥锁也可以同步线程的同步关系,甚至用全局变量加互斥锁也可以。但是信号量是最简单最专业的吧
    GeruzoniAnsasu
        4
    GeruzoniAnsasu  
       7 天前
    https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange

    spinlock 的典型场景呗

    B 线程读 flag, A 线程写 flag
    billlee
        5
    billlee  
       7 天前
    @LcDraven 嗯,对。Semaphore 更简单一点,刚才没想到
    jujusama
        6
    jujusama  
       7 天前
    在 Widget 的构造内将 mutex.lock(),在 function A 内使用 std::unique_lock(mutex, std::adopt_lock);
    A1st0n
        7
    A1st0n  
       6 天前
    使用条件变量:
    ```
    #include <iostream>
    #include <mutex>
    #include <condition_variable>
    #include <thread>
    #include <chrono>

    class Widget {
    public:
    void funcA() {
    std::unique_lock lock(mutex_);
    std::cout << "funcA starts\n";
    std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟操作
    std::cout << "funcA ends\n";

    // 标记 A 已完成,通知等待的线程
    funcA_done_ = true;
    cv_.notify_one();
    }

    void funcB() {
    // 等待 funcA 完成
    std::unique_lock lock(cv_mutex_);
    cv_.wait(lock, [this] { return funcA_done_; });

    std::unique_lock file_lock(mutex_);
    std::cout << "funcB starts\n";
    std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟操作
    std::cout << "funcB ends\n";
    }

    private:
    std::mutex mutex_; // 用于文件操作的互斥锁
    std::mutex cv_mutex_; // 用于条件变量的互斥锁
    std::condition_variable cv_; // 条件变量
    bool funcA_done_ = false; // 标志 funcA 是否已完成
    };

    // 封装函数
    void execA(Widget* w) {
    w->funcA();
    }

    void execB(Widget* w) {
    w->funcB();
    }

    int main() {
    Widget w;

    // 使用 std::jthread
    std::jthread threadA(execA, &w); // 在一个线程里执行 A 函数
    std::jthread threadB(execB, &w); // 在另一个线程里执行 B 函数

    return 0;
    }
    ```
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2435 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 16:06 · PVG 00:06 · LAX 08:06 · JFK 11:06
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.