• 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的基本用法如下代码。

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. std::mutex g_lock;
    7. void func()
    8. {
    9. g_lock.lock();
    10. cout << "enter thread: " << std::this_thread::get_id() << endl;
    11. std::this_thread::sleep_for(std::chrono::seconds(1));
    12. cout << "leaving thread: " << std::this_thread::get_id() << endl;
    13. g_lock.unlock();
    14. }
    15. ///g++ mutex.cpp -lpthread
    16. int main()
    17. {
    18. std::thread t1(func);
    19. std::thread t2(func);
    20. std::thread t3(func);
    21. t1.join();
    22. t2.join();
    23. t3.join();
    24. return 0;
    25. }

            输出结果如下:

    1. enter thread: 140569127851776
    2. leaving thread: 140569127851776
    3. enter thread: 140568859412224
    4. leaving thread: 140568859412224
    5. enter thread: 140568590972672
    6. leaving thread: 140568590972672

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

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. std::mutex g_lock;
    7. void func()
    8. {
    9. std::lock_guard locker(g_lock);///出了作用域之后自动解锁
    10. cout << "enter thread: " << std::this_thread::get_id() << endl;
    11. std::this_thread::sleep_for(std::chrono::seconds(1));
    12. cout << "leaving thread: " << std::this_thread::get_id() << endl;
    13. }
    14. ///g++ mutex.cpp -lpthread
    15. int main()
    16. {
    17. std::thread t1(func);
    18. std::thread t2(func);
    19. std::thread t3(func);
    20. t1.join();
    21. t2.join();
    22. t3.join();
    23. return 0;
    24. }

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

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

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. struct Complex
    7. {
    8. public:
    9. Complex(){i = 20;}
    10. void mul(int x)
    11. {
    12. printf("%s %s %d\n", __FILE__, __func__, __LINE__);
    13. g_mutex.lock();
    14. ///std::lock_guard locker(g_mutex);
    15. i *= x;
    16. printf("%s %s %d\n", __FILE__, __func__, __LINE__);
    17. g_mutex.unlock();
    18. }
    19. void div(int x)
    20. {
    21. printf("%s %s %d\n", __FILE__, __func__, __LINE__);
    22. g_mutex.lock();
    23. ///std::lock_guard locker(g_mutex);
    24. i /= x;
    25. printf("%s %s %d\n", __FILE__, __func__, __LINE__);
    26. g_mutex.unlock();
    27. }
    28. void both(int x, int y)
    29. {
    30. ///std::lock_guard locker(g_mutex);
    31. g_mutex.lock();
    32. printf("%s %s %d\n", __FILE__, __func__, __LINE__);
    33. mul(x);
    34. div(y);
    35. g_mutex.unlock();
    36. printf("%s %s %d\n", __FILE__, __func__, __LINE__);
    37. }
    38. private:
    39. int i;
    40. std::mutex g_mutex;
    41. };
    42. ///g++ mutex.cpp -lpthread
    43. int main()
    44. {
    45. Complex complex;
    46. complex.both(2, 4);
    47. return 0;
    48. }

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

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. struct Complex
    7. {
    8. public:
    9. Complex(){i = 20;}
    10. void mul(int x)
    11. {
    12. printf("%s %s %d\n", __FILE__, __func__, __LINE__);
    13. g_mutex.lock();
    14. ///std::lock_guard locker(g_mutex);
    15. i *= x;
    16. printf("%s %s %d\n", __FILE__, __func__, __LINE__);
    17. g_mutex.unlock();
    18. }
    19. void div(int x)
    20. {
    21. printf("%s %s %d\n", __FILE__, __func__, __LINE__);
    22. g_mutex.lock();
    23. ///std::lock_guard locker(g_mutex);
    24. i /= x;
    25. printf("%s %s %d\n", __FILE__, __func__, __LINE__);
    26. g_mutex.unlock();
    27. }
    28. void both(int x, int y)
    29. {
    30. ///std::lock_guard locker(g_mutex);
    31. g_mutex.lock();
    32. printf("%s %s %d\n", __FILE__, __func__, __LINE__);
    33. mul(x);
    34. div(y);
    35. g_mutex.unlock();
    36. printf("%s %s %d\n", __FILE__, __func__, __LINE__);
    37. }
    38. private:
    39. int i;
    40. std::recursive_mutex g_mutex;
    41. };
    42. void func()
    43. {
    44. Complex complex;
    45. complex.both(2, 4);
    46. }
    47. ///g++ mutex.cpp -lpthread
    48. int main()
    49. {
    50. thread t1(func);
    51. t1.join();
    52. return 0;
    53. }

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

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

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

    带超时的互斥量std::timed_mutex

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

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

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. std::timed_mutex g_mutex;
    7. void work()
    8. {
    9. std::chrono::milliseconds timeout(1000);
    10. while(true)
    11. {
    12. if (g_mutex.try_lock_for(timeout))
    13. {
    14. cout << std::this_thread::get_id() << ": do work with the mutex" << endl;
    15. std::chrono::milliseconds sleepDuration(5000);
    16. std::this_thread::sleep_for(sleepDuration);
    17. g_mutex.unlock();
    18. std::this_thread::sleep_for(sleepDuration);
    19. }
    20. else
    21. {
    22. cout << std::this_thread::get_id() << ": do work without the mutex" << endl;
    23. std::chrono::milliseconds sleepDuration(2000);
    24. std::this_thread::sleep_for(sleepDuration);
    25. }
    26. }
    27. }
    28. ///g++ mutex.cpp -lpthread
    29. int main()
    30. {
    31. std::thread t1(work);
    32. std::thread t2(work);
    33. t1.join();
    34. t2.join();
    35. return 0;
    36. }

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

  • 相关阅读:
    M5311连接HTTPS服务器下载bin文件(干货)
    .NET周报 【2月第4期 2023-02-25】
    网络面试-0x12 UDP和TCP的区别以及应用场景
    mongodb(快速上手)(一)
    PL/SQL安装并配置多个环境的数据库实例连接、登录用户
    《PyTorch深度学习实践》第三讲 反向传播
    四种类型自编码器AutoEncoder理解及代码实现
    警惕!外贸常见的一些骗局!
    WebGL笔记:使用鼠标绘制多个线条应用及绘制动感线性星座
    调试记录 单片机GD32F103C8T6(兆易创新) 程序烧写完成但是没有现象 (自己做的板子)
  • 原文地址:https://blog.csdn.net/qq_25048473/article/details/134611237