[编程基础] 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 小心地 ...
随机推荐
- [渲染层错误] [jsbridge] invoke remoteDebugInfo fail: too eayly.
1.问题描述 建立新的小程序项目时.控制台报错 [渲染层错误] [jsbridge] invoke remoteDebugInfo fail: too eayly. 2.解决方法 修改调试基础库的版本 ...
- 一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这里是参考B站上的大佬做的面试题笔记.大家也可以去看视频讲解!!! 文章目录 31.线程池复用的原理 32.spring是什么? 33.对Aop的理解 34.对IOC的理解 35.BeanFactor ...
- 齐博x1商业模块仅限一个国际域名使用
应用市场的所有商业模块 仅授权一个国际域名,大家不要试图复制到其它国际域名下使用. 仅支持一个国际域名使用,二级域名不限,但前提需要先用 www.开头的国际域名先安装,然后再到二级域名安装,并且二级域 ...
- 【神经网络】丢弃法(dropout)
丢弃法是一种降低过拟合的方法,具体过程是在神经网络传播的过程中,随机"沉默"一些节点.这个行为让模型过度贴合训练集的难度更高. 添加丢弃层后,训练速度明显上升,在同样的轮数下测试集 ...
- Element Ui 安装以及配置
npm 安装 推荐使用 npm 的方式安装,它能更好地和 webpack 打包工具配合使用. npm i element-ui -S 引入 Element 你可以引入整个 Element,或是根据需要 ...
- centos7离线安装PHP7
环境 centos7.9 PHP7.4.30 准备工作 在编译PHP时会提示一些包版本不够或者缺少某些包,一般选择yum来安装缺少的包,但因为是离线安装,所以可以手动配置本地yum源.先看一下系统版本 ...
- spring-ioc知识点
1.bean管理 -spring创建对象 -在spring的配置文件中,使用bean标签.标签里添加对应的属性.就可以实现对象的创建 -在bean标签中有很多属性 -id属性:唯一的标识 -class ...
- C#结构体大小问题
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServi ...
- 2022春每日一题:Day 8
题目:[HNOI2003]激光炸弹 二维前缀和,扫大小为m*m的矩形,取最大即可. 代码: #include <cstdio> #include <cstdlib> #incl ...
- Fastjsonfan反序列化(一)
前置知识 Fastjson 是一个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象. Fastjson 可以操作任何 Java 对象 ...