C++11互斥量的具体使用

 更新时间:2023年11月27日 11:39:17   作者:铲灰  
互斥量是一种同步原语,是一种线程同步的手段,用来保护多线程同时访问的共享数据,本文主要介绍了C++11互斥量的具体使用,感兴趣的可以了解一下

互斥量是一种同步原语,是一种线程同步的手段,用来保护多线程同时访问的共享数据。

C++11中提供了如下4种语义的互斥量(mutex):

1、std::mutex:独占的互斥量,不能递归使用。

2、std::mutex_mutex:带超时的独占互斥量,不能递归使用。

3、std::recursive_mutex:递归互斥量,不带超时功能。

4、std::recursive_timed_mutex:带超时的递归互斥量

独占互斥量std::mutex

这些互斥量的基本接口很相似,一般用法是通过lock()方法来阻塞线程,直到获得互斥量的所有权为止。在线程获得互斥量并完成任务之后,就必须使用unlock()来解除对互斥量的占用,lock()和unlock()必须成对出现。try_lock()尝试锁定互斥量,如果成功则返回true,如果失败则返回false,它是阻塞的。std::mutex的基本用法如下代码。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;

std::mutex g_lock;

void func()
{
    g_lock.lock();

    cout << "enter thread: " << std::this_thread::get_id() << endl;

    std::this_thread::sleep_for(std::chrono::seconds(1));

    cout << "leaving thread: " << std::this_thread::get_id() << endl;

    g_lock.unlock();
}

///g++ mutex.cpp -lpthread
int main()
{
    std::thread t1(func);
    std::thread t2(func);
    std::thread t3(func);

    t1.join();
    t2.join();
    t3.join();

    return 0;
}

输出结果如下:

enter thread: 140569127851776
leaving thread: 140569127851776
enter thread: 140568859412224
leaving thread: 140568859412224
enter thread: 140568590972672
leaving thread: 140568590972672

使用lock_guard可以简化lock/unlock的写法,同时也更安全,因为lock_guard在构造函数时会自动锁定互斥量,而在退出作用域后进行析构时就会自动解锁,从而保证了互斥量的正确操作,避免忘记unlock操作,因此,应尽量用lock_guard。lock_guard用到了RAII技术,这种技术在类的构造函数中分配资源,在析构函数中释放资源,保证资源在出了作用域之后就释放,上面的例子使用lock_guard后更简洁,代码如下:

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;

std::mutex g_lock;

void func()
{
    std::lock_guard<std::mutex> locker(g_lock);///出了作用域之后自动解锁

    cout << "enter thread: " << std::this_thread::get_id() << endl;

    std::this_thread::sleep_for(std::chrono::seconds(1));

    cout << "leaving thread: " << std::this_thread::get_id() << endl;

}

///g++ mutex.cpp -lpthread
int main()
{
    std::thread t1(func);
    std::thread t2(func);
    std::thread t3(func);

    t1.join();
    t2.join();
    t3.join();

    return 0;
}

递归的独占互斥量std::recursive_mutex

递归锁允许同一个线程多次获得该互斥锁,可以用来解决同一个线程需要多次获取互斥量死锁的问题。在下面的代码中,一个线程多次获取同一个互斥量时会发生死锁。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;

struct Complex
{
public:
    Complex(){i = 20;}

    void mul(int x)
    {
        printf("%s %s %d\n", __FILE__, __func__, __LINE__);
        g_mutex.lock();
        ///std::lock_guard<std::mutex> locker(g_mutex);
        i *= x;
        printf("%s %s %d\n", __FILE__, __func__, __LINE__);
        g_mutex.unlock();
    }

    void div(int x)
    {
        printf("%s %s %d\n", __FILE__, __func__, __LINE__);
        g_mutex.lock();
        ///std::lock_guard<std::mutex> locker(g_mutex);
        i /= x;
        printf("%s %s %d\n", __FILE__, __func__, __LINE__);
        g_mutex.unlock();
    }

    void both(int x, int y)
    {
        ///std::lock_guard<std::mutex> locker(g_mutex);
        g_mutex.lock();
        printf("%s %s %d\n", __FILE__, __func__, __LINE__);
        mul(x);
        div(y);
        g_mutex.unlock();
        printf("%s %s %d\n", __FILE__, __func__, __LINE__);
    }

private:
    int i;
    std::mutex g_mutex;
};


///g++ mutex.cpp -lpthread
int main()
{
    Complex complex;
    complex.both(2, 4);

    return 0;
}

这个例子运行起来就会发生死锁,因为在调用both时获取了互斥量,之后再调用mul又要获取相同的互斥量,但是这个互斥量已经被当前线程获取了,无法释放,这时就会发生死锁。要解决这个死锁的问题,一个简单的办法就是用递归锁:std::recursive_mutex,它允许同一个线程多次获得互斥量。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;

struct Complex
{
public:
    Complex(){i = 20;}

    void mul(int x)
    {
        printf("%s %s %d\n", __FILE__, __func__, __LINE__);
        g_mutex.lock();
        ///std::lock_guard<std::recursive_mutex> locker(g_mutex);
        i *= x;
        printf("%s %s %d\n", __FILE__, __func__, __LINE__);
        g_mutex.unlock();
    }

    void div(int x)
    {
        printf("%s %s %d\n", __FILE__, __func__, __LINE__);
        g_mutex.lock();
        ///std::lock_guard<std::recursive_mutex> locker(g_mutex);
        i /= x;
        printf("%s %s %d\n", __FILE__, __func__, __LINE__);
        g_mutex.unlock();
    }

    void both(int x, int y)
    {
        ///std::lock_guard<std::recursive_mutex> locker(g_mutex);
        g_mutex.lock();
        printf("%s %s %d\n", __FILE__, __func__, __LINE__);
        mul(x);
        div(y);
        g_mutex.unlock();
        printf("%s %s %d\n", __FILE__, __func__, __LINE__);
    }

private:
    int i;
    std::recursive_mutex g_mutex;
};

void func()
{
    Complex complex;
    complex.both(2, 4);
}

///g++ mutex.cpp -lpthread
int main()
{
    thread t1(func);

    t1.join();

    return 0;
}

需要注意的是尽量不要使用递归锁,主要原因如下:

1、需要用到递归锁定的多线程互斥处理往往本身就是可以简化的,允许递归互斥很容易放纵复杂逻辑的产生,从而导致一些多线程同步引起的问题。

2、递归锁比起非递归锁,效率会低一些。

带超时的互斥量std::timed_mutex

std::timed_mutex是超时的独占锁,主要用在获取锁时增加超时等待功能,因为有时不知道获取锁需要多久,为了不至于一直在等待获取互斥量,就设置一个等待超时时间,在超时后还可以做其他事情。

std::timed_mutex比std::mutex多了两个超时获取锁的接口:try_lock_for和try_lock_until,这两个接口是用来设置获取互斥量的超时时间,使用时可以用while循环取不断地获取互斥量。std::timed_mutex的基本用法如下所示。

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;

std::timed_mutex g_mutex;

void work()
{
    std::chrono::milliseconds timeout(1000);

    while(true)
    {
        if (g_mutex.try_lock_for(timeout))
        {
            cout << std::this_thread::get_id() << ": do work with the mutex" << endl;

            std::chrono::milliseconds sleepDuration(5000);

            std::this_thread::sleep_for(sleepDuration);

            g_mutex.unlock();

            std::this_thread::sleep_for(sleepDuration);
        }
        else
        {
            cout << std::this_thread::get_id() << ": do work without the mutex" << endl;

            std::chrono::milliseconds sleepDuration(2000);

            std::this_thread::sleep_for(sleepDuration);
        }
    }
}


///g++ mutex.cpp -lpthread
int main()
{
    std::thread t1(work);

    std::thread t2(work);

    t1.join();

    t2.join();

    return 0;
}

在上面的例子中,通过一个while循环不断地去获取超时锁,如果超时还没有获取到锁就会休眠,再继续获取超时锁。

到此这篇关于C++11互斥量的具体使用的文章就介绍到这了,更多相关C++11互斥量内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Opencv实现联合双边滤波

    Opencv实现联合双边滤波

    这篇文章主要为大家详细介绍了Opencv实现联合双边滤波,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-10-10
  • C++中的类与对象深度解析

    C++中的类与对象深度解析

    这篇文章主要为大家详细介绍了C++中的类与对象,使用数据库,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • 浅谈C++高并发场景下读多写少的优化方案

    浅谈C++高并发场景下读多写少的优化方案

    本文主要介绍了浅谈C++高并发场景下读多写少的优化方案,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • C++基于QWidget和QLabel实现图片缩放,拉伸与拖拽

    C++基于QWidget和QLabel实现图片缩放,拉伸与拖拽

    这篇文章主要为大家详细介绍了C++如何基于QWidget和QLabel实现图片缩放、拉伸与拖拽等功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-02-02
  • 基于C语言实现泛型编程详解

    基于C语言实现泛型编程详解

    对于C而言,想实现泛型编程并非易事,甚至可以说非常繁琐,一大堆坑。最主要也没有现成的轮子可用。本文就来详细为大家讲讲C语言如何实现泛型编程详解,需要的可以参考一下
    2022-07-07
  • MFC Frame-Splitter模型实例原理解析

    MFC Frame-Splitter模型实例原理解析

    这篇文章主要介绍了MFC Frame-Splitter模型实例原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • VScode中添加头文件和源文件(C/C++)的方法

    VScode中添加头文件和源文件(C/C++)的方法

    使用VSCode编译C/C++时,会存在找不到头文件的情况,下面这篇文章主要给大家介绍了关于VScode中添加头文件和源文件(C/C++)的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-08-08
  • C++可以函数重载而C不可以的原因分析

    C++可以函数重载而C不可以的原因分析

    函数重载是指在同一个作用域内,可以定义多个函数,它们具有相同的名称但是参数列表不同,为什么C++可以函数重载而C不可以,接下来就有小编来给大家介绍一下C++可以函数重载而C不可以的原因,需要的朋友可以参考下
    2023-12-12
  • C语言模式实现C++继承和多态的实例代码

    C语言模式实现C++继承和多态的实例代码

    本篇文章主要介绍了C语言模式实现C++继承和多态的实例代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • C语言中数据是如何存储在内存中的

    C语言中数据是如何存储在内存中的

    使用编程语言进行编程时,需要用到各种变量来存储各种信息。变量保留的是它所存储的值的内存位置。这意味着,当您创建一个变量时,就会在内存中保留一些空间。您可能需要存储各种数据类型的信息,操作系统会根据变量的数据类型,来分配内存和决定在保留内存中存储什么
    2022-04-04

最新评论