linux互斥锁和条件变量的关系

linux互斥锁和条件变量的关系

https://blog.csdn.net/qq_33775402/column/info/16273

https://blog.csdn.net/xiexievv/article/details/57318386

mutex体现的是一种竞争,我离开了,通知你进来。

cond体现的是一种协作,我准备好了,通知你开始吧。

  • 互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起配合使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其他的某个线程改变了条件变量,他将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。

  • 两个线程操作同一临界区时,通过互斥锁保护,若A线程已经加锁,B线程再加锁时候会被阻塞,直到A释放锁,B再获得锁运行,进程B必须不停的主动获得锁、检查条件、释放锁、再获得锁、再检查、再释放,一直到满足运行的条件的时候才可以(而此过程中其他线程一直在等待该线程的结束),这种方式是比较消耗系统的资源的。而条件变量同样是阻塞,还需要通知才能唤醒,线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,该线程就休眠了,应该仍阻塞在这里,等待条件满足后被唤醒,节省了线程不断运行浪费的资源。这个过程一般用while语句实现。当线程B发现被锁定的变量不满足条件时会自动的释放锁并把自身置于等待状态,让出CPU的控制权给其它线程。其它线程
    此时就有机会去进行操作,当修改完成后再通知那些由于条件不满足而陷入等待状态的线程。这是一种通知模型的同步方式,大大的节省了CPU的计算资源,减少了线程之间的竞争,而且提高了线程之间的系统工作的效率。这种同步方式就是条件变量。

  • 以上说明可能有点抽象,考虑这样的简单场景:通过伪代码说明。
    A线程从队列中取元素,B线程往队列中存放元素。不考虑免锁的实现。需要一个mutex用来保护队列的一致性,避免两个线程同时操作队列破坏数据结构。

当队列为空的时候,A需要不断的探测队列状态 :

while(1)
{ 
if(队列为空)
休眠10s
else
    {
        加锁
        取元素
        解锁
     }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这就有一个问题,可能在刚进入休眠时,B放入元素了,但仍然需要休眠完整个10s的时间。造成不必要的延迟。当然如果不sleep,也可以,但会造成不必要的CPU开销。使用基于条件变量的事件通知唤醒机制,就可以避免这些问题。

一旦B放入元素完成后就执行pthread_cond_signal(),当前阻塞的线程就会立即被唤醒开始干活儿。
while(1) {
    pthread_mutex_lock();
    pthread_cond_wait();
    取元素;
    pthread_mutex_unlock();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 条件变量都用互斥锁进行保护,条件变量状态的改变都应该先锁住互斥锁,pthread_cond_wait()需要传入一个已经加锁的互斥锁,该函数把调用线程加入等待条件的调用列表中,然后释放互斥锁,在条件满足从而离开pthread_cond_wait()时,mutex将被重新加锁,这两个函数是原子操作。
    可以消除条件发生和线程睡眠等待条件发生间的时间间隙。其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量才能计算条件。

  • 感觉可以总结为:条件变量用于某个线程需要在某种条件成立时才去保护它将要操作的临界区,这种情况从而避免了线程不断轮询检查该条件是否成立而降低效率的情况,这是实现了效率提高。。。在条件满足时,自动退出阻塞,再加锁进行操作。

  • 以上是关于效率问题,此外互斥锁还有一个缺点就是会造成死锁。
    例如线程A和线程B都需要独占使用2个资源,但是他们都分别先占据了一个资源,然后又相互等待另外一个资源的释放,这样就形成了一个死锁。

  • 条件变量起到了阻塞和唤醒线程的作用,所以通常互斥锁要和条件变量配合。
    为了解决以上问题,条件变量常和互斥锁一起使用,条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。

  • 理解条件变量

    条件变量可以用来管理thread间的通信。一个线程可以等待在一个条件变量上,直到发生某个事件。

    考虑一个场景,一个线程访问一个队列时,发现队列为空,他只能等待 直到其他线程将一个节点添加到队列中。这种情况就需要用到条件变量。(当然,用低效的轮询也可以,不停的去判断队列中是否有节点)
    C++11 标准库提供的 condition_variable 概览

    condition_variable cv{};  //默认构造函数
    cv.~condition_variable();  //析构函数

    ----------
    cv.notify_one();   //随机通知一个等待该条件变量的线程
    cv.notify_all();    //通知全部等待该条件变量的线程   
    ----------

    cv.wait(lck);           //等待  直到  被唤醒  (存在为唤醒 )
    cv.wait(lck,pred);      // 相当于 while(!pared())wait(lck)
    x=cv.wait_util(lck,tp);   //等待  直到 被唤醒或者到达某个时间点
                            //返回值 x 如果超时 则 x==timeout,否者x==cv_status::no_timeou

    b=cv.wait_until(lck,tp,pred) // 等价于: while(!pared())
                                     //     { if(wait_until(lck,tp)==cv_sattus::timeout);}b=pred()

    x=cv.wait_for(lck,d);   //等待 直到被唤醒或者超时   @d 超时时间  
    b=cv.wait_for(lck,d,pred);  //等价于 b=cv.wait_until(lck,steady_clock::now()+d,std::move(pre));

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20

    剖析等待操作

        cv.wait(lck) 需要与一个互斥锁搭配使用
        代码通常如下

        //伪代码  
        ...
        condition   cond;    //条件变量  
        mutex   mu;           //互斥锁
        /*
           进入临界区
        */
        mu.lock();
        while(v.is_empty())
        {
          cond.wait(&mu);
        }
         ...do something  ..
        mu.unlock();
        /*
         退出临界区
        */

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17

        在调用wait时:
        首先该线程需要获取到保护v的锁 进入临界取,因为 v 应该是多个线程可以访问的。
        在wait函数执行如下操作:
        1. wait函数内部首先 mu.unlock() 释放锁 ;
        2. 然后进入等待;
        3. 如果被唤醒,则调用mu.lock() 再次获取锁。

        为什么wait函数要在正真开始等待之前释放锁?
        因为 :
        1. 变量 v 需要其他线程 需要获取锁 然后给v添加元素,以便可以更改v.is_empty()这个条件;
        2. 让其他线程也有机会进入临界区 等待相同的条件

    C++11并发编程-条件变量(condition_variable)详解

    2018年08月16日 17:28:16 下一页盛夏花开 阅读数:5883

    总结的很详细,打算记录下来。

    原文地址:https://www.2cto.com/kf/201506/411327.html

    头文件主要包含了与条件变量相关的类和函数。相关的类包括 std::condition_variable和 std::condition_variable_any,还有枚举类型std::cv_status。另外还包括函数 std::notify_all_at_thread_exit(),下面分别介绍一下以上几种类型。

    std::condition_variable 类介绍

    std::condition_variable是条件变量,更多有关条件变量的定义参考维基百科。Linux下使用 Pthread库中的 pthread_cond_*() 函数提供了与条件变量相关的功能, Windows 则参考 MSDN

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

    std::condition_variable 对象通常使用 std::unique_lock 来等待,如果需要使用另外的 lockable 类型,可以使用std::condition_variable_any类,本文后面会讲到 std::condition_variable_any 的用法。

    ?

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    <code class="hljs cpp">#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 << '\n';

    }

     

    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...\n";

        go(); // go!

     

      for (auto & th:threads)

            th.join();

     

        return 0;

    }</std::mutex></std::mutex></condition_variable></mutex></thread></iostream></code>

    结果:

    ?

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    <code class="hljs lasso">10 threads ready to race...

    thread 1

    thread 0

    thread 2

    thread 3

    thread 4

    thread 5

    thread 6

    thread 7

    thread 8

    thread 9</code>

    好了,对条件变量有了一个基本的了解之后,我们来看看 std::condition_variable 的各个成员函数。

    std::condition_variable 的拷贝构造函数被禁用,只提供了默认构造函数。

    std::condition_variable::wait() 介绍:

    ?

    1

    2

    3

    4

    5

    <code class="hljs d">void wait (unique_lock<mutex>& lck);

     

     

    template <class predicate="">

      void wait (unique_lock<mutex>& lck, Predicate pred);</mutex></class></mutex></code>

    std::condition_variable提供了两种 wait() 函数。当前线程调用 wait() 后将被阻塞(此时当前线程应该获得了锁(mutex),不妨设获得锁 lck),直到另外某个线程调用 notify_* 唤醒了当前线程。

    在线程被阻塞时,该函数会自动调用 lck.unlock() 释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。另外,一旦当前线程获得通知(notified,通常是另外某个线程调用 notify_* 唤醒了当前线程),wait()函数也是自动调用 lck.lock(),使得lck的状态和 wait 函数被调用时相同。

    在第二种情况下(即设置了 Predicate),只有当 pred 条件为false 时调用 wait() 才会阻塞当前线程,并且在收到其他线程的通知后只有当 pred 为 true 时才会被解除阻塞。因此第二种情况类似以下代码:

    ?

    1

    <code class="hljs perl">while (!pred()) wait(lck);</code>

    ?

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    <code class="hljs cpp">#include <iostream>                // std::cout

    #include <thread>                // std::thread, std::this_thread::yield

    #include <mutex>                // std::mutex, std::unique_lock

    #include <condition_variable>    // std::condition_variable

     

    std::mutex mtx;

    std::condition_variable cv;

     

    int cargo = 0;

    bool shipment_available()

    {

        return cargo != 0;

    }

     

    // 消费者线程.

    void consume(int n)

    {

        for (int i = 0; i < n; ++i) {

            std::unique_lock <std::mutex> lck(mtx);

            cv.wait(lck, shipment_available);

            std::cout << cargo << '\n';

            cargo = 0;

        }

    }

     

    int main()

    {

        std::thread consumer_thread(consume, 10); // 消费者线程.

     

        // 主线程为生产者线程, 生产 10 个物品.

        for (int i = 0; i < 10; ++i) {

            while (shipment_available())

                std::this_thread::yield();

            std::unique_lock <std::mutex> lck(mtx);

            cargo = i + 1;

            cv.notify_one();

        }

     

        consumer_thread.join();

     

        return 0;

    }</std::mutex></std::mutex></condition_variable></mutex></thread></iostream></code>

    ?

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    <code class="hljs ">1

    2

    3

    4

    5

    6

    7

    8

    9

    10</code>

    std::condition_variable::wait_for() 介绍

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值