zoukankan      html  css  js  c++  java
  • [Notes] C++ condition variable 和mutex

    (参考:https://www.cnblogs.com/haippy/p/3252041.html

    std::condition_variable 是条件变量。当 std::condition_variable 对象的某个 wait 函数被调用的时候,它使用 std::unique_lock(通过 std::mutex) 来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的 std::condition_variable 对象上调用了 notification 函数来唤醒当前线程。

    std::condition_variable 对象通常使用 std::unique_lock<std::mutex> 来等待。

    #include <iostream>                // std::cout
    #include <thread>                // std::thread
    #include <mutex>                // std::mutex, std::unique_lock
    #include <condition_variable>    // std::condition_variable
    
    std::mutex mtx; // 全局互斥锁.
    std::condition_variable cv; // 全局条件变量.
    bool ready = false; // 全局标志位.
    
    void do_print_id(int id)
    {
        std::unique_lock <std::mutex> lck(mtx);
        while (!ready) // 如果标志位不为 true, 则等待...
            cv.wait(lck); // 当前线程被阻塞, 当全局标志位变为 true 之后,
        // 线程被唤醒, 继续往下执行打印线程编号id.
        std::cout << "thread " << id << '
    ';
    }
    
    void go()
    {
        std::unique_lock <std::mutex> lck(mtx);
        ready = true; // 设置全局标志位为 true.
        cv.notify_all(); // 唤醒所有线程.
    }
    
    int main()
    {
        std::thread threads[10];
        // spawn 10 threads:
        for (int i = 0; i < 10; ++i)
            threads[i] = std::thread(do_print_id, i);
    
        std::cout << "10 threads ready to race...
    ";
        go(); // go!
    
      for (auto & th:threads)
            th.join();
    
        return 0;
    }
    

      

    以leetcode的一道例题来说明这个使用方法。

    首先是题目:

    我们提供了一个类:
    
    public class Foo {
      public void one() { print("one"); }
      public void two() { print("two"); }
      public void three() { print("three"); }
    }
    三个不同的线程将会共用一个 Foo 实例。
    
    线程 A 将会调用 one() 方法
    线程 B 将会调用 two() 方法
    线程 C 将会调用 three() 方法
    请设计修改程序,以确保 two() 方法在 one() 方法之后被执行,three() 方法在 two() 方法之后被执行。
    
     
    
    示例 1:
    
    输入: [1,2,3]
    输出: "onetwothree"
    解释: 
    有三个线程会被异步启动。
    输入 [1,2,3] 表示线程 A 将会调用 one() 方法,线程 B 将会调用 two() 方法,线程 C 将会调用 three() 方法。
    正确的输出是 "onetwothree"。
    
    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/print-in-order
    著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
    

      

    可以看出,leetcode的测试主线程会实例化Foo,但是只有一个对象,然后该对象会被传入3个线程,三个线程分别调用one, two, three。要求不论怎么运行,输出结果都是1,2,3。很显然,最保险的方式是利用共享的信号量来逐级阻塞two,three的运行。这里采用C++的条件变量和互斥锁来解决。

    首先要分析的有一点,因为只有一个对象,而这个对象被传入三个线程,所以三个线程会指向同一个内存(因为传给三个线程的是指针),所以对象了里面定义的成员变量是共享的。本人的实现如下:

    class Foo {
    private:
        int counter=1;
        std::mutex mtx; // 全局互斥锁.
        std::condition_variable cv1;
        std::condition_variable cv2;
    public:
        Foo() {
            
        }
    
        void first(function<void()> printFirst) {
            std::unique_lock <std::mutex> lck(mtx); //使用同一个互斥锁的进行会相互影响
            // printFirst() outputs "first". Do not change or remove this line.
            printFirst();
            counter++;
            cv1.notify_all();
        }
    
        void second(function<void()> printSecond) {
            std::unique_lock <std::mutex> lck(mtx); //使用同一个互斥锁的进行会相互影响
            // printSecond() outputs "second". Do not change or remove this line.
            while(counter!=2){
                cv1.wait(lck);
            }
            printSecond();
            counter++;
            cv2.notify_all();
        }
    
        void third(function<void()> printThird) {
            std::unique_lock <std::mutex> lck(mtx); //使用同一个互斥锁的进行会相互影响
            while(counter!=3){
                cv2.wait(lck);
            }
            // printThird() outputs "third". Do not change or remove this line.
            printThird();
        }
    };
    

      

    首先定义4个类成员:

        int counter=1;
        std::mutex mtx; // 全局互斥锁.
        std::condition_variable cv1;
        std::condition_variable cv2;
    

      

    这里counter用来标记轮到那个函数运行;

    mtx是全局互斥的锁,等于一个跨线程的信号。

    cv1和cv2是两个条件变量。

    对于第一个函数:

    void first(function<void()> printFirst) {
            std::unique_lock <std::mutex> lck(mtx); //使用同一个互斥锁的进行会相互影响
            // printFirst() outputs "first". Do not change or remove this line.
            printFirst();
            counter++;
            cv1.notify_all();
        }
    

      

    毫无疑问,它必须是第一个打印,所以前面不需要给它设置障碍(函数第一句初始化一个锁对象也可以不要,应该显然没用到,我这里只是统一写一下)。

    当打印过后,我们对counter计数,指示下一个要运行的是打印2的函数。同时告诉条件变量cv1,所有在互斥锁mtx影响下,等待条件变量cv1的线程都可以继续运行了(无需等待)。

    对于第二个函数:

    void second(function<void()> printSecond) {
            std::unique_lock <std::mutex> lck(mtx); //使用同一个互斥锁的进行会相互影响
            // printSecond() outputs "second". Do not change or remove this line.
            while(counter!=2){
                cv1.wait(lck);
            }
            printSecond();
            counter++;
            cv2.notify_all();
        }
    

      

    这个函数的第一句是有用的,使用mtx初始化一个互斥锁。当counter不是2的时候,就使用绑定了互斥锁的条件变量把当前变量阻塞。当cv1提供了提醒后,就可以唤醒当前线程。然后当前线程会判断counter是否为2,为2则打印two。。然后counter加计数,然后将cv2对应的线程解开。

  • 相关阅读:
    Account group in ERP and its mapping relationship with CRM partner group
    错误消息Number not in interval XXX when downloading
    错误消息Form of address 0001 not designated for organization
    Algorithm类介绍(core)
    梯度下降与随机梯度下降
    反思
    绘图: matplotlib核心剖析
    ORB
    SIFT
    Harris角点
  • 原文地址:https://www.cnblogs.com/immortalBlog/p/11680321.html
Copyright © 2011-2022 走看看