[编程基础] C++多线程入门5-使用互斥锁解决资源竞争
原始C++标准仅支持单线程编程。新的C++标准(称为C++11或C++0x)于2011年发布。在C++11中,引入了新的线程库。因此运行本文程序需要C++至少符合C++11标准。
5 使用互斥锁解决资源竞争
在本文中,我们将讨论如何使用互斥锁来保护多线程环境中的共享数据并避免资源竞争。为了解决多线程环境中的资源竞争,我们需要互斥锁,即每个线程都需要在修改或读取共享数据之前锁定互斥锁,并且在修改数据之后,每个线程都应解锁互斥锁。
5.1 std::mutex
在C++11线程库中,互斥锁位于mutex头文件中。表示互斥锁的类是std::mutex类
互斥锁有两种重要的方法:
- lock()
- unlock()
我们已经在上一篇文章中使用多线程钱包解释了资源竞争。在本文中,我们将看到如何使用std::mutex修复该多线程钱包中的资源竞争。由于电子钱包提供了在电子钱包中添加资金的服务,并且在不同线程之间使用了相同的电子钱包对象,因此我们需要在电子钱包的addMoney()方法中添加锁定,即在增加电子钱包的货币之前获取锁并在离开该钱包之前释放锁功能。让我们看一下代码:
内部维护货币并提供服务/功能的钱包类,即addMoney()。
该成员函数首先获取一个锁,然后将钱包对象的内部货币增加指定的数量,然后释放该锁。
#include<iostream>
#include<thread>
#include<vector>
#include<mutex>
class Wallet
{
int mMoney;
std::mutex mutex;
public:
Wallet() :mMoney(0) {}
int getMoney() { return mMoney; }
void addMoney(int money)
{
mutex.lock();
for (int i = 0; i < money; ++i)
{
mMoney++;
}
mutex.unlock();
}
};
现在,让我们创建5个线程,所有这些线程将共享Wallet类的同一对象,并使用其addMoney()成员函数并行向内部货币添加100000。因此,如果最初在钱包中的钱为0。那么在完成所有线程的执行后,在Wallet中的钱应该为500000。并且此互斥锁可确保电子钱包中的资金最终为500000。让我们测试一下:
#include<iostream>
#include<thread>
#include<vector>
#include<mutex>
class Wallet
{
int mMoney;
std::mutex mutex;
public:
Wallet() :mMoney(0) {}
int getMoney() { return mMoney; }
void addMoney(int money)
{
mutex.lock();
for (int i = 0; i < money; ++i)
{
mMoney++;
}
mutex.unlock();
}
};
int testMultithreadedWallet()
{
Wallet walletObject;
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.push_back(std::thread(&Wallet::addMoney, &walletObject, 100000));
}
for (int i = 0; i < threads.size(); i++)
{
threads.at(i).join();
}
return walletObject.getMoney();
}
int main()
{
int val = 0;
for (int k = 0; k < 10; k++)
{
if ((val = testMultithreadedWallet()) != 500000)
{
std::cout << "Error at count = " << k << " Money in Wallet = " << val << std::endl;
//break;
}
else
{
std::cout << "Now count = " << k << " Money in Wallet = " << val << std::endl;
//break;
}
}
return 0;
}
输出为:
Now count = 0 Money in Wallet = 500000
Now count = 1 Money in Wallet = 500000
Now count = 2 Money in Wallet = 500000
Now count = 3 Money in Wallet = 500000
Now count = 4 Money in Wallet = 500000
Now count = 5 Money in Wallet = 500000
Now count = 6 Money in Wallet = 500000
Now count = 7 Money in Wallet = 500000
Now count = 8 Money in Wallet = 500000
Now count = 9 Money in Wallet = 500000
可以保证不会发现钱包中的钱少于500000的单个情况。因为addMoney中的互斥锁可确保一旦一个线程完成了钱的修改,则只有其他任何线程才能修改Wallet中的钱。
但是,如果我们忘记在功能结束时解锁互斥锁,该怎么办?在这种情况下,一个线程将退出而不释放锁,而其他线程将保持等待状态。如果锁定互斥锁后发生某些异常,则可能发生这种情况。为了避免这种情况,我们应该使用std::lock_guard。
5.2 std::lock_guard
Lock_Guard是一个类模板,它实现了互斥锁的RAII。它将互斥体包装在其对象中,并将附加的互斥体锁定在其构造函数中。当调用它的析构函数时,它会释放互斥锁。让我们看看代码:
#include<iostream>
#include<thread>
#include<vector>
#include<mutex>
class Wallet
{
int mMoney;
std::mutex mutex;
public:
Wallet() :mMoney(0) {}
int getMoney() { return mMoney; }
void addMoney(int money)
{
// 在构造函数中,它锁定互斥锁 In constructor it locks the mutex
std::lock_guard<std::mutex> lockGuard(mutex);
for (int i = 0; i < money; ++i)
{
// If some exception occurs at this poin then destructor of lockGuard will be called due to stack unwinding.
// 如果在此位置发生异常,则由于堆栈展开,将调用lockGuard的析构函数。
mMoney++;
}
// Once function exits, then destructor of lockGuard Object will be called. In destructor it unlocks the mutex.
//一旦函数退出,则析构函数,将调用析构函数中的lockGuard对象,它解锁互斥锁。
}
};
int testMultithreadedWallet()
{
Wallet walletObject;
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.push_back(std::thread(&Wallet::addMoney, &walletObject, 100000));
}
for (int i = 0; i < threads.size(); i++)
{
threads.at(i).join();
}
return walletObject.getMoney();
}
int main()
{
int val = 0;
for (int k = 0; k < 10; k++)
{
if ((val = testMultithreadedWallet()) != 500000)
{
std::cout << "Error at count = " << k << " Money in Wallet = " << val << std::endl;
//break;
}
else
{
std::cout << "Now count = " << k << " Money in Wallet = " << val << std::endl;
//break;
}
}
return 0;
}
输出为:
Now count = 0 Money in Wallet = 500000
Now count = 1 Money in Wallet = 500000
Now count = 2 Money in Wallet = 500000
Now count = 3 Money in Wallet = 500000
Now count = 4 Money in Wallet = 500000
Now count = 5 Money in Wallet = 500000
Now count = 6 Money in Wallet = 500000
Now count = 7 Money in Wallet = 500000
Now count = 8 Money in Wallet = 500000
Now count = 9 Money in Wallet = 500000
5.3 参考
https://thispointer.com//c11-multithreading-part-5-using-mutex-to-fix-race-conditions/
[编程基础] C++多线程入门5-使用互斥锁解决资源竞争的更多相关文章
- 多任务-python实现-同步概念,互斥锁解决资源竞争(2.1.4)
@ 目录 1.同步的概念 2.解决线程同时修改全局变量的方式 3.互斥锁 1.同步的概念 同步就是协同步调,按照预定的先后次序进行运行,如你说完我在说 同步在子面上容易理解为一起工作 其实不是,同指的 ...
- [编程基础] C++多线程入门7-条件变量介绍
原始C++标准仅支持单线程编程.新的C++标准(称为C++11或C++0x)于2011年发布.在C++11中,引入了新的线程库.因此运行本文程序需要C++至少符合C++11标准. 文章目录 7 条件变 ...
- [编程基础] C++多线程入门6-事件处理的需求
原始C++标准仅支持单线程编程.新的C++标准(称为C++11或C++0x)于2011年发布.在C++11中,引入了新的线程库.因此运行本文程序需要C++至少符合C++11标准. 文章目录 6 事件处 ...
- [编程基础] C++多线程入门8-从线程返回值
原始C++标准仅支持单线程编程.新的C++标准(称为C++11或C++0x)于2011年发布.在C++11中,引入了新的线程库.因此运行本文程序需要C++至少符合C++11标准. 8 从线程返回值 8 ...
- [编程基础] C++多线程入门4-数据共享和资源竞争
原始C++标准仅支持单线程编程.新的C++标准(称为C++11或C++0x)于2011年发布.在C++11中,引入了新的线程库.因此运行本文程序需要C++至少符合C++ 11标准. 4 数据共享和资源 ...
- [编程基础] C++多线程入门1-创建线程的三种不同方式
原始C++标准仅支持单线程编程.新的C++标准(称为C++11或C++0x)于2011年发布.在C++11中,引入了新的线程库.因此运行本文程序需要C++至少符合C++11标准. 1 创建线程的三种不 ...
- [编程基础] C++多线程入门10-packaged_task示例
原始C++标准仅支持单线程编程.新的C++标准(称为C++11或C++0x)于2011年发布.在C++11中,引入了新的线程库.因此运行本文程序需要C++至少符合C++11标准. 文章目录 10 pa ...
- [编程基础] C++多线程入门9-async教程和示例
原始C++标准仅支持单线程编程.新的C++标准(称为C++11或C++0x)于2011年发布.在C++11中,引入了新的线程库.因此运行本文程序需要C++至少符合C++11标准. 文章目录 9 asy ...
- [编程基础] C++多线程入门3-小心地将参数传递给线程
原始C++标准仅支持单线程编程.新的C++标准(称为c++11或c++0x)于2011年发布.在c++11中,引入了新的线程库.因此运行本文程序需要C++至少符合c++11标准. 文章目录 3 小心地 ...
随机推荐
- C++运算符重载(简单易懂)
转载:https://www.cnblogs.com/liuchenxu123/p/12538623.html 运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型. 你 ...
- 云原生时代的DevOps平台设计之道
开发人员与运维人员是 IT 领域很重要的两大人群,他们都会参与到各种业务系统的建设过程中去.DevOps 是近年间火爆起来的一种新理念,这种理念被很多人错误的解读为"由开发人员(Dev)学习 ...
- 5.pygame快速入门-精灵和精灵组
在之前案例中,图像加载.位置变化.绘制图像都需要编写代码分别处理 pygame提供了两个类简化开发步骤 pygame.sprite.Sprite #精灵,存储图像数据image和位置rect的对象 p ...
- $_SERVER["REQUEST_URI"],在 PHP 众多预定义服务器变量中,$_SERVER["REQUEST_URI"] 算是经常用到的,但是这个变量只有 apache 才支持
例如访问:http://localhost/index.php?app=lunbo获取到的$_SERVER["REQUEST_URI"]为"/index.php?app= ...
- UVA10763
菜鸡退役人来水黄了-- \(\sf{Solution}\) 搞不懂为什么要排序,这不是两个数组直接模拟的数数题吗. 读入后,对于每个学生,令他要去的学校以及他现在所在学校人数对应加一,再 check ...
- 题解 UVA439 骑士的移动 Knight Moves
前言 最近板子题刷多了-- 题意 一个 \(8\times 8\) 的棋盘,问马从起点到终点的最短步数为多少. \(\sf Solution\) 要求最短路径嘛,显然 bfs 更优. 读入 这个读入处 ...
- Istio(五):使用服务网格Istio进行流量路由
目录 一.模块概览 二.系统环境 三.简单路由 3.1 简单路由 四.Subset和DestinationRule 4.1 Subset 和 DestinationRule 4.2 Destinati ...
- 真正“搞”懂HTTP协议03之时间穿梭
上一篇我们简单的介绍了一下DoD模型和OSI模型,还着重的讲解了TCP的三次握手和四次挥手,让我们在空间层面,稍稍宏观的了解了HTTP所依赖的底层模型,那么这一篇,我们来追溯一下HTTP的历史,看一看 ...
- Re:从零开始教你使用 Sublime Text
目录 Re:从零开始教你使用 Sublime Text 0.前言 0-0.关于我为什么要写这篇文章 0-1.关于这篇文章会讲什么 0-2.适用人群 0-4.其他 0-5.无用的统计 1.安装和基础功能 ...
- C#之GCHandle
转载 略谈GCHandle C# - Marshal.StructureToPtr方法简介 Marshal类 两个方法StructureToPtr和PtrToStructure实现序列化 字节 数组 ...