zoukankan      html  css  js  c++  java
  • C++11并发之std::mutex

    C++11并发之std::thread

    本文概要:
    1、<mutex> 头文件。
    2、std::mutex。
    3、std::recursive_mutex。
    4、std::time_mutex。
    5、std::lock_guard 与 std::unique_lock。

    Mutex 又称互斥量,C++ 11中与 Mutex 相关的类(包括锁类型)和函数都声明在 #include<mutex> 头文件中,所以如果你需要使用 std::mutex,就必须包含 #include<mutex> 头文件。

    1、<mutex> 头文件。

    Mutex 系列类(四种)
    std::mutex,最基本的 Mutex 类。
    std::recursive_mutex,递归 Mutex 类。
    std::time_mutex,定时 Mutex 类。
    std::recursive_timed_mutex,定时递归 Mutex 类。
    Lock 类(两种)
    std::lock_guard,与 Mutex RAII 相关,方便线程对互斥量上锁。
    std::unique_lock,与 Mutex RAII 相关,方便线程对互斥量上锁,但提供了更好的上锁和解锁控制。
    其他类型
    std::once_flag
    std::adopt_lock_t
    std::defer_lock_t
    std::try_to_lock_t
    函数
    std::try_lock,尝试同时对多个互斥量上锁。
    std::lock,可以同时对多个互斥量上锁。
    std::call_once,如果多个线程需要同时调用某个函数,call_once 可以保证多个线程对该函数只调用一次。

    2、std::mutex。

    下面以 std::mutex 为例介绍 C++11 中的互斥量用法。
    std::mutex 是C++11 中最基本的互斥量,std::mutex 对象提供了独占所有权的特性——即不支持递归地对 std::mutex 对象上锁,而 std::recursive_lock 则可以递归地对互斥量对象上锁。
    std::mutex 的成员函数

    (1)构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。
    (2)lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:
         a)如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。
         b)如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
         c)如果当前互斥量被当前调用线程锁住,则会产生死锁 (deadlock) 。
    (3)unlock(), 解锁,释放对互斥量的所有权。
    (4)try_lock(),尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。线程调用该函数也会出现下面 3 种情况:
         a)如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用 unlock 释放互斥量。
         b)如果当前互斥量被其他线程锁住,则当前调用线程返回 false,而并不会被阻塞掉。
         c)如果当前互斥量被当前调用线程锁住,则会产生死锁 (deadlock) 。

    std::mutex的例子如下:
    #include<iostream> //std::cout
    #include<thread> //std::thread
    #include<mutex> //std::mutex
    #include<atomic> //std::atomic
    using namespace std;
    atomic_int counter{ 0 }; //原子变量
    mutex g_mtx; //互斥量
    void fun()
    {
    for (int i = 0; i < 1000000; ++i)
    {
    if (g_mtx.try_lock()) //尝试是否可以加锁
    {
    ++counter;
    g_mtx.unlock(); //解锁
    }
    }
    }
    int main()
    {
    thread threads[10];
    for (int i = 0; i < 10; ++i)
    {
    threads[i] = thread(fun);
    }
    for (auto & th : threads)
    {
    th.join();
    }
    cout << "counter=" << counter << endl;
    system("pause");
    return 0;
    }
    运行结果:
    counter=1342244
    从例子可知,10个线程不会产生死锁,由于 try_lock() ,尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞。但是这样会导致结果不正确,这也就是线程安全的问题,前面在 C++11并发之std::thread T7 中详细介绍了这个问题。

    3、std::recursive_mutex。

    如果一个线程中可能在执行中需要再次获得锁的情况,按常规的做法会出现死锁。
    例如:
    #include<iostream> //std::cout
    #include<thread> //std::thread
    #include<mutex> //std::mutex
    using namespace std;
    mutex g_mutex;
    void threadfun1()
    {
    cout << "enter threadfun1" << endl;
    lock_guard<mutex> lock(g_mutex);
    cout << "execute threadfun1" << endl;
    }
    void threadfun2()
    {
    cout << "enter threadfun2" << endl;
    lock_guard<mutex> lock(g_mutex);
    threadfun1();
    cout << "execute threadfun2" << endl;
    }
    int main()
    {
    threadfun2(); //死锁
    //Unhandled exception at 0x758BC42D in Project2.exe: Microsoft C++ exception: std::system_error at memory location 0x0015F140.
    return 0;
    }
    运行结果:
    enter threadfun2
    enter threadfun1
    //就会产生死锁
    此时就需要使用递归式互斥量 recursive_mutex 来避免这个问题。recursive_mutex不会产生上述的死锁问题,只是是增加锁的计数,但必须确保你unlock和lock的次数相同,其他线程才可能锁这个mutex。
    例如:
    #include<iostream> //std::cout
    #include<thread> //std::thread
    #include<mutex> //std::mutex
    using namespace std;
    recursive_mutex g_rec_mutex;
    void threadfun1()
    {
    cout << "enter threadfun1" << endl;
    lock_guard<recursive_mutex> lock(g_rec_mutex);
    cout << "execute threadfun1" << endl;
    }
    void threadfun2()
    {
    cout << "enter threadfun2" << endl;
    lock_guard<recursive_mutex> lock(g_rec_mutex);
    threadfun1();
    cout << "execute threadfun2" << endl;
    }
    int main()
    {
    threadfun2(); //利用递归式互斥量来避免这个问题
    return 0;
    }
    运行结果:
    enter threadfun2
    enter threadfun1
    execute threadfun1
    execute threadfun2
    结论:
    std::recursive_mutex 与 std::mutex 一样,也是一种可以被上锁的对象,但是和 std::mutex 不同的是,std::recursive_mutex 允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,std::recursive_mutex 释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),可理解为 lock() 次数和 unlock() 次数相同,除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。

    4、std::time_mutex。

    std::time_mutex 比 std::mutex 多了两个成员函数,try_lock_for(),try_lock_until()。
     
    try_lock_for 函数接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与 std::mutex 的 try_lock() 不同,try_lock 如果被调用时没有获得锁则直接返回 false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。
    例如:
    #include<iostream> //std::cout
    #include<thread> //std::thread
    #include<mutex> //std::mutex
    using namespace std;
    std::timed_mutex g_t_mtx;
    void fun()
    {
    while (!g_t_mtx.try_lock_for(std::chrono::milliseconds(200)))
    {
    cout << "-";
    }
    this_thread::sleep_for(std::chrono::milliseconds(1000));
    cout << "*" << endl;
    g_t_mtx.unlock();
    }
    int main()
    {
    std::thread threads[10];
    for (int i = 0; i < 10; i++)
    {
    threads[i] = std::thread(fun);
    }
    for (auto & th : threads)
    {
    th.join();
    }
    return 0;
    }
    运行结果:
    ------------------------------------*
    ----------------------------------------*
    -----------------------------------*
    ------------------------------*
    -------------------------*
    --------------------*
    ---------------*
    ----------*
    -----*
    *
    try_lock_until 函数则接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。

    5、std::lock_guard 与 std::unique_lock。

    上面介绍的方法对 mutex 的加解锁都是手动的,接下来介绍 std::lock_guard 与 std::unique_lock 对 mutex 进行自动加解锁。
    例如:
    #include<iostream> //std::cout
    #include<thread> //std::thread
    #include<mutex> //std::mutex
    #include<atomic> //std::atomic
    using namespace std;
    mutex g_mtx1;
    atomic_int num1{ 0 };
    void fun1()
    {
    for (int i = 0; i < 10000000; i++)
    {
    unique_lock<mutex> ulk(g_mtx1);
    num1++;
    }
    }
    mutex g_mtx2;
    atomic_int num2{ 0 };
    void fun2()
    {
    for (int i = 0; i < 10000000; i++)
    {
    lock_guard<mutex> lckg(g_mtx2);
    num2++;
    }
    }
    int main()
    {
    thread th1(fun1);
    thread th2(fun1);
    th1.join();
    th2.join();
    cout << "num1=" << num1 << endl;
    thread th3(fun2);
    thread th4(fun2);
    th3.join();
    th4.join();
    cout << "num2=" << num2 << endl;
    return 0;
    }
    运行结果:
    num1=20000000
    num2=20000000
    接下来,分析一下这两者的区别:
    (1)unique_lock。
    unique_lock<mutex> ulk(g_mtx1);
    线程没有 g_mtx1 的所有权,根据块语句的循环实现自动加解锁。
    线程根据 g_mtx1 属性,来判断是否可以加锁、解锁。
    (2)lock_guard。
    lock_guard<mutex> lckg(g_mtx2);
    线程拥有 g_mtx2 的所有权,实现自动加解锁。
    线程读取 g_mtx2 失败时,则一直等待,直到读取成功。
    线程会把  g_mtx2 一直占有,直到当前线程完成才释放,其它线程才能访问。
    ---------------------
    作者:liuker888
    来源:CSDN
    原文:https://blog.csdn.net/liuker888/article/details/46848957
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    ADF中遍历VO中的行数据(Iterator)
    程序中实现两个DataTable的Left Join效果(修改了,网上第二个DataTable为空,所处的异常)
    ArcGIS api for javascript——鼠标悬停时显示信息窗口
    ArcGIS api for javascript——查询,然后单击显示信息窗口
    ArcGIS api for javascript——查询,立刻打开信息窗口
    ArcGIS api for javascript——显示多个查询结果
    ArcGIS api for javascript——用图表显示查询结果
    ArcGIS api for javascript——查询没有地图的数据
    ArcGIS api for javascript——用第二个服务的范围设置地图范围
    ArcGIS api for javascript——显示地图属性
  • 原文地址:https://www.cnblogs.com/mmc9527/p/10427929.html
Copyright © 2011-2022 走看看