• C++多线程学习05 超时锁,递归锁与共享锁


    一、超时锁timed_mutex

    功能:避免长时间死锁,可以记录锁获取情况,多次超时,可以记录日志,获取错误情况

    在04中可以由于try_lock()不会阻塞该线程而是一直占着CPU资源,因此加入sleep_for(100ms)延时一会阻塞下该线程给其他线程一点机会,然而这的延时是调用的this_thread下的函数:

    if (!mux.try_lock())
            {
                cout << "." << flush;
                this_thread::sleep_for(100ms);
                continue;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    也可以将延时做为锁的构造函数的参数,使用超时锁timed_mutex:

    timed_mutex tmux;
    
    void ThreadMainTime(int i)
    {
    
    
        for (;;)
        {
            if (!tmux.try_lock_for(chrono::milliseconds(500)))
            { 
                cout << i << "[try_lock_for timeout]" << endl;
                continue;
            }
            cout << i << "[in]" << endl;
            this_thread::sleep_for(2000ms);
            tmux.unlock();
            this_thread::sleep_for(1ms);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    同样的为了确保unlock能释放资源,最后延时一下:

    创建了三个线程,每个线程尝试解锁之后先阻塞500ms
    疑问:三个线程彼此detach为什么打印的不会乱呢?

    int main(int argc, char* argv[])
    {
     
        for (int i = 0; i < 3; i++)
        {
            thread th(ThreadMainTime, i + 1);
            th.detach();
        }
        getchar();
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    [in]表示线程进去了
    [try_lock_for timeout]表示进入失败
    在这里插入图片描述

    二、递归锁(可重入锁)recursive_mutex

    同一个线程中的同一把锁可以锁多次。避免了一些不必要的死锁
    组合业务 用到同一个锁
    如果组合业务使用的是同一个锁,但是不是递归锁,就需要在进入这些线程前要先加锁再解锁,这个加锁解锁的间隔cpu的资源可能被其他线程占着了

    recursive_mutex rmux;
    void Task1()
    {
        rmux.lock();
        cout << "task1 [in]" << endl;
        rmux.unlock();
    }
    void Task2()
    {
        rmux.lock();
        cout << "task2 [in]" << endl;
        rmux.unlock();
    }
    void ThreadMainRec(int i)
    {
        for (;;)
        {
            rmux.lock(); 
            Task1();
            cout << i << "[in]" << endl;
            this_thread::sleep_for(2000ms);
            Task2();
            rmux.unlock();
            this_thread::sleep_for(1ms);
        }
    }
    
    • 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

    就算是递归锁也是加锁几次就要解锁几次

    int main(int argc, char* argv[])
    {
        
        for (int i = 0; i < 3; i++)
        {
            thread th(ThreadMainRec, i + 1);
            th.detach();
        }
        getchar();
     }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    可以看到123号ThreadMainRec交替切换,每个ThreadMainRec执行一次task1与task2
    在这里插入图片描述

    三、共享锁shared_mutex

    经常会遇到这样的场景:
    很多(100)个线程同时读一个资源,不会有问题,当有线程对这个资源修改时,这100个资源都要等待,此时再有其他资源想对这个资源修改也同样需要等待

    总结下:
    读资源的线程在读时其他线程可以读,但是不能写
    写资源的线程在写时其他线程既不能读也不能写
    因此不同需要的线程使用不同的锁,读的线程使用读的锁,写的线程使用写的锁
    某个线程只读的话就用读的锁
    某个线程准备写的话先用读的锁,再用写的锁,然后再对资源进行修改

    现在祭出这两把锁:
    c++14 共享超时互斥锁 shared_timed_mutex
    c++17 共享互斥 shared_mutex

    以C++14标准为例:
    shared_timed_mutex stmux;
    使用写的锁:stmux.lock();
    使用读的锁:stmux.lock_shared();

    shared_timed_mutex stmux;
    
    void ThreadRead(int i)
    {
        for (;;)
        {
            stmux.lock_shared();
            cout << i << " Read" << endl;
            //this_thread::sleep_for(500ms);
            stmux.unlock_shared();
            this_thread::sleep_for(1ms);
        }
    }
    void ThreadWrite(int i)
    {
        for (;;)
        {
            stmux.lock(); //互斥锁 写入
            cout << i << " Write" << endl;
            this_thread::sleep_for(300ms);
            stmux.unlock();
            this_thread::sleep_for(1ms);
        }
    }
    int main(int argc, char* argv[])
    {
        for (int i = 0; i < 3; i++)
        {
            thread th(ThreadWrite, i + 1);
            th.detach();
        }
        for (int i = 0; i < 3; i++)
        {
            thread th(ThreadRead, i + 1);
            th.detach();
        }
        getchar();
        return 0;
    }
    
    • 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

    想拿到写的锁,得先确保读的锁释放与写的锁释放
    想拿到读的锁,只用确保写的锁的释放
    可以看到读线程经常出现在同一行,三个读线程通过for循环创建之后,如果资源没有被写的锁锁住,三个线程都能拿的读的锁,一起进入cout的线程任务中,当一个线程的cout << i << " Read" << endl;没有运行完时(汇编之后不止一行)另一个线程的cout << i << " Read" << endl;送到CPU上运行,因此会出现同一行的现象,而写线程就不会这样,他要确保当前没有读线程也没有线程
    在这里插入图片描述

  • 相关阅读:
    基于数组的SCL算法实现S7-1200_1500多通道模拟量批量处理的方法
    【四数之和】
    适用于ARM开发板的Armbian Linux22.08发布
    Java大数 -- BigInteger类
    Redis作为缓存,mysql的数据如何与redis进行同步?
    MyBatis 案例
    【Docker】apache 容器化部署
    【Redis】原理篇:Redis过期删除与内存淘汰
    Framework 为何被称为 Android 开发者必修?
    Go Web——Gin使用中间件
  • 原文地址:https://blog.csdn.net/qq_42567607/article/details/125530759