原始C++标准仅支持单线程编程。新的C++标准(称为C++11或C++0x)于2011年发布。在C++11中,引入了新的线程库。因此运行本文程序需要C++至少符合C++11标准。

5 使用互斥锁解决资源竞争

在本文中,我们将讨论如何使用互斥锁来保护多线程环境中的共享数据并避免资源竞争。为了解决多线程环境中的资源竞争,我们需要互斥锁,即每个线程都需要在修改或读取共享数据之前锁定互斥锁,并且在修改数据之后,每个线程都应解锁互斥锁。

5.1 std::mutex

在C++11线程库中,互斥锁位于mutex头文件中。表示互斥锁的类是std::mutex类
互斥锁有两种重要的方法:

  1. lock()
  2. unlock()

我们已经在上一篇文章中使用多线程钱包解释了资源竞争。在本文中,我们将看到如何使用std::mutex修复该多线程钱包中的资源竞争。由于电子钱包提供了在电子钱包中添加资金的服务,并且在不同线程之间使用了相同的电子钱包对象,因此我们需要在电子钱包的addMoney()方法中添加锁定,即在增加电子钱包的货币之前获取锁并在离开该钱包之前释放锁功能。让我们看一下代码:
内部维护货币并提供服务/功能的钱包类,即addMoney()。
该成员函数首先获取一个锁,然后将钱包对象的内部货币增加指定的数量,然后释放该锁。

  1. #include<iostream>
  2. #include<thread>
  3. #include<vector>
  4. #include<mutex>
  5. class Wallet
  6. {
  7. int mMoney;
  8. std::mutex mutex;
  9. public:
  10. Wallet() :mMoney(0) {}
  11. int getMoney() { return mMoney; }
  12. void addMoney(int money)
  13. {
  14. mutex.lock();
  15. for (int i = 0; i < money; ++i)
  16. {
  17. mMoney++;
  18. }
  19. mutex.unlock();
  20. }
  21. };

现在,让我们创建5个线程,所有这些线程将共享Wallet类的同一对象,并使用其addMoney()成员函数并行向内部货币添加100000。因此,如果最初在钱包中的钱为0。那么在完成所有线程的执行后,在Wallet中的钱应该为500000。并且此互斥锁可确保电子钱包中的资金最终为500000。让我们测试一下:

  1. #include<iostream>
  2. #include<thread>
  3. #include<vector>
  4. #include<mutex>
  5. class Wallet
  6. {
  7. int mMoney;
  8. std::mutex mutex;
  9. public:
  10. Wallet() :mMoney(0) {}
  11. int getMoney() { return mMoney; }
  12. void addMoney(int money)
  13. {
  14. mutex.lock();
  15. for (int i = 0; i < money; ++i)
  16. {
  17. mMoney++;
  18. }
  19. mutex.unlock();
  20. }
  21. };
  22. int testMultithreadedWallet()
  23. {
  24. Wallet walletObject;
  25. std::vector<std::thread> threads;
  26. for (int i = 0; i < 5; ++i) {
  27. threads.push_back(std::thread(&Wallet::addMoney, &walletObject, 100000));
  28. }
  29. for (int i = 0; i < threads.size(); i++)
  30. {
  31. threads.at(i).join();
  32. }
  33. return walletObject.getMoney();
  34. }
  35. int main()
  36. {
  37. int val = 0;
  38. for (int k = 0; k < 10; k++)
  39. {
  40. if ((val = testMultithreadedWallet()) != 500000)
  41. {
  42. std::cout << "Error at count = " << k << " Money in Wallet = " << val << std::endl;
  43. //break;
  44. }
  45. else
  46. {
  47. std::cout << "Now count = " << k << " Money in Wallet = " << val << std::endl;
  48. //break;
  49. }
  50. }
  51. return 0;
  52. }

输出为:

  1. Now count = 0 Money in Wallet = 500000
  2. Now count = 1 Money in Wallet = 500000
  3. Now count = 2 Money in Wallet = 500000
  4. Now count = 3 Money in Wallet = 500000
  5. Now count = 4 Money in Wallet = 500000
  6. Now count = 5 Money in Wallet = 500000
  7. Now count = 6 Money in Wallet = 500000
  8. Now count = 7 Money in Wallet = 500000
  9. Now count = 8 Money in Wallet = 500000
  10. Now count = 9 Money in Wallet = 500000

可以保证不会发现钱包中的钱少于500000的单个情况。因为addMoney中的互斥锁可确保一旦一个线程完成了钱的修改,则只有其他任何线程才能修改Wallet中的钱。
但是,如果我们忘记在功能结束时解锁互斥锁,该怎么办?在这种情况下,一个线程将退出而不释放锁,而其他线程将保持等待状态。如果锁定互斥锁后发生某些异常,则可能发生这种情况。为了避免这种情况,我们应该使用std::lock_guard。

5.2 std::lock_guard

Lock_Guard是一个类模板,它实现了互斥锁的RAII。它将互斥体包装在其对象中,并将附加的互斥体锁定在其构造函数中。当调用它的析构函数时,它会释放互斥锁。让我们看看代码:

  1. #include<iostream>
  2. #include<thread>
  3. #include<vector>
  4. #include<mutex>
  5. class Wallet
  6. {
  7. int mMoney;
  8. std::mutex mutex;
  9. public:
  10. Wallet() :mMoney(0) {}
  11. int getMoney() { return mMoney; }
  12. void addMoney(int money)
  13. {
  14. // 在构造函数中,它锁定互斥锁 In constructor it locks the mutex
  15. std::lock_guard<std::mutex> lockGuard(mutex);
  16. for (int i = 0; i < money; ++i)
  17. {
  18. // If some exception occurs at this poin then destructor of lockGuard will be called due to stack unwinding.
  19. // 如果在此位置发生异常,则由于堆栈展开,将调用lockGuard的析构函数。
  20. mMoney++;
  21. }
  22. // Once function exits, then destructor of lockGuard Object will be called. In destructor it unlocks the mutex.
  23. //一旦函数退出,则析构函数,将调用析构函数中的lockGuard对象,它解锁互斥锁。
  24. }
  25. };
  26. int testMultithreadedWallet()
  27. {
  28. Wallet walletObject;
  29. std::vector<std::thread> threads;
  30. for (int i = 0; i < 5; ++i) {
  31. threads.push_back(std::thread(&Wallet::addMoney, &walletObject, 100000));
  32. }
  33. for (int i = 0; i < threads.size(); i++)
  34. {
  35. threads.at(i).join();
  36. }
  37. return walletObject.getMoney();
  38. }
  39. int main()
  40. {
  41. int val = 0;
  42. for (int k = 0; k < 10; k++)
  43. {
  44. if ((val = testMultithreadedWallet()) != 500000)
  45. {
  46. std::cout << "Error at count = " << k << " Money in Wallet = " << val << std::endl;
  47. //break;
  48. }
  49. else
  50. {
  51. std::cout << "Now count = " << k << " Money in Wallet = " << val << std::endl;
  52. //break;
  53. }
  54. }
  55. return 0;
  56. }

输出为:

  1. Now count = 0 Money in Wallet = 500000
  2. Now count = 1 Money in Wallet = 500000
  3. Now count = 2 Money in Wallet = 500000
  4. Now count = 3 Money in Wallet = 500000
  5. Now count = 4 Money in Wallet = 500000
  6. Now count = 5 Money in Wallet = 500000
  7. Now count = 6 Money in Wallet = 500000
  8. Now count = 7 Money in Wallet = 500000
  9. Now count = 8 Money in Wallet = 500000
  10. Now count = 9 Money in Wallet = 500000

5.3 参考

https://thispointer.com//c11-multithreading-part-5-using-mutex-to-fix-race-conditions/

[编程基础] C++多线程入门5-使用互斥锁解决资源竞争的更多相关文章

  1. 多任务-python实现-同步概念,互斥锁解决资源竞争(2.1.4)

    @ 目录 1.同步的概念 2.解决线程同时修改全局变量的方式 3.互斥锁 1.同步的概念 同步就是协同步调,按照预定的先后次序进行运行,如你说完我在说 同步在子面上容易理解为一起工作 其实不是,同指的 ...

  2. [编程基础] C++多线程入门7-条件变量介绍

    原始C++标准仅支持单线程编程.新的C++标准(称为C++11或C++0x)于2011年发布.在C++11中,引入了新的线程库.因此运行本文程序需要C++至少符合C++11标准. 文章目录 7 条件变 ...

  3. [编程基础] C++多线程入门6-事件处理的需求

    原始C++标准仅支持单线程编程.新的C++标准(称为C++11或C++0x)于2011年发布.在C++11中,引入了新的线程库.因此运行本文程序需要C++至少符合C++11标准. 文章目录 6 事件处 ...

  4. [编程基础] C++多线程入门8-从线程返回值

    原始C++标准仅支持单线程编程.新的C++标准(称为C++11或C++0x)于2011年发布.在C++11中,引入了新的线程库.因此运行本文程序需要C++至少符合C++11标准. 8 从线程返回值 8 ...

  5. [编程基础] C++多线程入门4-数据共享和资源竞争

    原始C++标准仅支持单线程编程.新的C++标准(称为C++11或C++0x)于2011年发布.在C++11中,引入了新的线程库.因此运行本文程序需要C++至少符合C++ 11标准. 4 数据共享和资源 ...

  6. [编程基础] C++多线程入门1-创建线程的三种不同方式

    原始C++标准仅支持单线程编程.新的C++标准(称为C++11或C++0x)于2011年发布.在C++11中,引入了新的线程库.因此运行本文程序需要C++至少符合C++11标准. 1 创建线程的三种不 ...

  7. [编程基础] C++多线程入门10-packaged_task示例

    原始C++标准仅支持单线程编程.新的C++标准(称为C++11或C++0x)于2011年发布.在C++11中,引入了新的线程库.因此运行本文程序需要C++至少符合C++11标准. 文章目录 10 pa ...

  8. [编程基础] C++多线程入门9-async教程和示例

    原始C++标准仅支持单线程编程.新的C++标准(称为C++11或C++0x)于2011年发布.在C++11中,引入了新的线程库.因此运行本文程序需要C++至少符合C++11标准. 文章目录 9 asy ...

  9. [编程基础] C++多线程入门3-小心地将参数传递给线程

    原始C++标准仅支持单线程编程.新的C++标准(称为c++11或c++0x)于2011年发布.在c++11中,引入了新的线程库.因此运行本文程序需要C++至少符合c++11标准. 文章目录 3 小心地 ...

随机推荐

  1. java的分页原理详解

    首先先创建一个Student实体类. import java.io.Serializable; import java.util.Map; public class Student implement ...

  2. 驱动开发:内核特征码扫描PE代码段

    在笔者上一篇文章<驱动开发:内核特征码搜索函数封装>中为了定位特征的方便我们封装实现了一个可以传入数组实现的SearchSpecialCode定位函数,该定位函数其实还不能算的上简单,本章 ...

  3. 去除router-link中的下划线

    文章目录 1.设置router-link的样式 2.效果展示 1.设置router-link的样式 text-decoration: none; 2.效果展示

  4. Codeforces Round #830 (Div. 2) A-D

    比赛链接 A 题解 知识点:贪心,数论. 先求出序列最大公约数 \(d\) ,如果为 \(1\) 直接输出 \(0\) . 否则,尝试用最后一个数操作, \(gcd(d,n) = 1\) 则可以,花费 ...

  5. 部署redis-cluster

    1.环境准备 ☆ 每个Redis 节点采用相同的相同的Redis版本.相同的密码.硬件配置 ☆ 所有Redis服务器必须没有任何数据 #所有主从节点执行: [root@ubuntu2004 ~]#ba ...

  6. Rocky之Mysql-MHA高可用

    9.半同步复制 安装插件三种方法: 第一种: mysql>INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so' 安装 在 ...

  7. 如何在.NET程序崩溃时自动创建Dump?

    今天在浏览张队转载文章的留言时,遇到一个读者问了这样的问题,如下图所示: 首先能明确的一点是"程序崩溃退出了是不能用常规的方式dump的",因为整个进程树都已经退出.现场已经无法使 ...

  8. Windows 环境搭建 PostgreSQL 物理复制高可用架构数据库服务

    PostgreSQL 高可用数据库的常见搭建方式主要有两种,逻辑复制和物理复制,上周已经写过了关于在Windows环境搭建PostgreSQL逻辑复制的教程,这周来记录一下 物理复制的搭建方法. 首先 ...

  9. Ubuntu 下安装 redis 并且设置远程登陆和密码

    安装redis sudo apt-get install -y redis-server 更改配置 sudo vim /etc/redis/redis.conf 如果不知道怎么找 直接在命令行模式下输 ...

  10. Oracle:ORA-39006、ORA-39213解决办法

    执行Oracle数据库导入,遇到报错ORA-39006: internal error.ORA-39213: Metadata processing is not available.这还是第一次遇到 ...