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

4 数据共享和资源竞争

在多线程环境中,线程之间的数据共享非常容易。但是,这种易于共享的数据可能会导致应用程序出现问题。这样的问题之一就是资源竞争。

4.1 资源竞争

竞争条件是多线程应用程序中出现的一种错误。当两个或多个线程并行执行一组操作时,它们将访问同一内存位置。同样,其中的一个或多个线程会修改该内存位置中的数据,这有时会导致意外结果。这称为竞争条件。

竞赛条件通常不会每次都出现,因此通常很难找到和复制。仅当两个或多个线程的相对执行顺序导致意外结果时,它们才会发生。让我们通过一个例子来理解。

让我们创建一个Wallet类,它在内部维护money并提供一个服务/功能,即addMoney()。此成员函数按指定的计数递增钱包对象的内部货币。

class Wallet
{
int mMoney;
public:
Wallet() :mMoney(0) {}
int getMoney() {
return mMoney;
}
void addMoney(int money)
{
for (int i = 0; i < money; ++i)
{
mMoney++;
}
}
};

现在,让我们创建5个线程,所有这些线程将共享Wallet类的同一对象,并使用其addMoney()成员函数并行向内部货币添加100000(这个数字要足够大,否则无效果)。因此,如果最初在钱包中的钱为0。那么在完成所有线程的执行后,在Wallet中的钱应该为500000。但是,由于所有线程正在同时修改共享数据,因此在某些情况下,最终钱包中的钱可能少于500000。让我们测试一下:

#include <iostream>
#include <vector>
#include <thread> class Wallet
{
int mMoney;
public:
Wallet() :mMoney(0) {}
int getMoney() {
return mMoney;
}
void addMoney(int money)
{
for (int i = 0; i < money; ++i)
{
mMoney++;
}
}
}; 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;
}
}
return 0;
}

输出为:

Error at count = 0 Money in Wallet = 360389
Error at count = 1 Money in Wallet = 420648
Error at count = 2 Money in Wallet = 382707
Error at count = 3 Money in Wallet = 397744
Error at count = 4 Money in Wallet = 280937
Error at count = 5 Money in Wallet = 248475
Error at count = 6 Money in Wallet = 240935
Error at count = 7 Money in Wallet = 320526
Error at count = 8 Money in Wallet = 328090
Error at count = 9 Money in Wallet = 370140

由于同一Wallet类对象的addMoney()成员函数执行了5次,因此其内部货币预计为500000。但是由于addMoney()成员函数并行执行,因此在某些情况下mMoney会比500000小得多。

为什么会这样?

每个线程并行递增相同的“mMoney”成员变量。虽然看起来只有一行,但是这个“mMoney++”实际上被转换成三个机器命令。

  1. 将“ mMoney”变量值加载到寄存器中
  2. 增量寄存器的值
  3. 用寄存器的值更新变量“ mMoney”

现在假设在特殊情况下,上述命令的执行顺序如下:

在这种情况下,一个增量将被忽略,因为不是将“mMoney”变量递增两次,而是不同的寄存器递增,并且“mMoney”变量的值被覆盖。

假设在此方案之前,mMoney为46,如上图所示,它增加了2倍,因此预期结果为48。但是由于上述方案中的资源竞争,mMoney的最终值将仅为47。

4.2 如何解决比赛条件?

为了解决这个问题,我们需要使用Lock机制,即每个线程需要在修改或读取共享数据之前获取一个锁,并且在修改数据之后,每个线程都应该解锁该锁。我们将在下一篇文章中讨论这一点。

4.3 参考

https://thispointer.com/c11-multithreading-part-4-data-sharing-and-race-conditions/

[编程基础] C++多线程入门4-数据共享和资源竞争的更多相关文章

  1. [编程基础] C++多线程入门5-使用互斥锁解决资源竞争

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

  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++多线程入门1-创建线程的三种不同方式

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

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

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

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

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

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

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

  9. [编程基础] C++多线程入门2-连接和分离线程

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

随机推荐

  1. 一文讲清楚 JVM Safe Point

    大家好,我是树哥. 关于 Safe Point 是 JVM 中很关键的一个概念,但我估计有不少同学不是很懂.于是今天跟大家来深入聊聊 Safe Point,希望通过这篇文章能解答这样几个问题: 什么是 ...

  2. Jmeter——BeanShell 内置变量vars、props、prev的使用

    在使用Jmeter过程中,或多或少都会接触些BeanShell,它会使工具的使用,变得更灵活. Jmeter中关于BeanShell的有: 1.BeanShell Sampler 取样器:完成Bean ...

  3. 计算机网络(Learning Records)

    背景:没想到本专业并不开设这门课程,感觉过于逆天,之前开发的时候了解过相关知识 但是从来没有系统地学过,就自己看了书,总结一下 参考:<TCP/IP详解 卷1:协议> 概述 大多数网络应用 ...

  4. linux开放端口并测试

    开放端口操作 开放端口 先开启防火墙(才能用开启端口的命令) systemctl start firewalld.service 开放端口 firewall-cmd --zone=public --a ...

  5. Vue中使用Switch开关用来控制商品的上架与下架情况、同时根据数据库商品的状态反应到前台、前台修改商品状态保存到数据库

    一般后台对商品的信息管理.包含商品的上架与下架.为了提高用户的体验.将商品上下架的操作做成开关的形式.同时后台数据库中保存的商品状态能够根据开关状态改变. 1.效果展示 这种效果:== 当开关是开启状 ...

  6. 前端框架Vue------>第三天学习(1)

    ` 文章目录 10 .组件基础 10.1 .什么是组件 11.什么是计算属性 10 .组件基础 10.1 .什么是组件 件是可复用的Vue实例,说白了就是一组可以重复使用的模板 <!DOCTYP ...

  7. Landau-Vishkin

    基础算法 假设我们有两个字符串:,每个字符串由A C G T四个字母组成. 在两个字符串上,都有三种可能的编辑操作(突变): 删除某个字符 在某个位置插入字符 改变某个字符 每一个编辑操作都有惩罚值. ...

  8. 三十三、HPA实现自动扩缩容

    通过HPA实现业务应用的动态扩缩容 HPA控制器介绍 当系统资源过高的时候,我们可以使用如下命令来实现 Pod 的扩缩容功能 $ kubectl -n luffy scale deployment m ...

  9. Python基础之模块:3、os模块 sys模块 json模块 json实战

    目录 一.os模块 1.创建目录 2.删除目录 3.查看指定路径下目录内容 4.删除/重命名文件 5.获取/切换当前目录 6.动态获取项目根路径 7.拼接/切割路径 8.判断文件.目录是否存在 9.判 ...

  10. Codeforces Round #812 (Div. 2) D. Tournament Countdown(交互题)

    记录一下第一次写交互题 题目大意:一共有1<<n个人参加一场竞标赛,需要你通过比较两人的胜场来判断谁晋级,最终获得第一名 最多1/3*2^(n+1)次询问,每次询问query(a,b),如 ...