首页 > 编程语言 >[编程基础] C++多线程入门4-数据共享和资源竞争

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

时间:2022-12-18 23:23:59浏览次数:74  
标签:count int mMoney C++ Wallet 线程 数据共享 Error 多线程

date:  2020-05-29 16:51:33 +0800
tags:
  - 编程基础

原始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/

标签:count,int,mMoney,C++,Wallet,线程,数据共享,Error,多线程
From: https://www.cnblogs.com/luohenyueji/p/16991258.html

相关文章

  • [编程基础] C++多线程入门1-创建线程的三种不同方式
    date:2020-05-2916:30:50+0800tags:-编程基础原始C++标准仅支持单线程编程。新的C++标准(称为C++11或C++0x)于2011年发布。在C++11中,引入了新的线程库。因此运行......
  • Modern C++ Overview (综览)
    PartI:Language(第一篇:语言)大局观——简直像个新语言给出一个完整实例,展示(几乎)所有新特性的样貌,让学员从真实代码中一次性窥得(几乎)全豹,得知即将面对的新知和挑战......
  • C++ Web(HTTP)开发 10 大利器
    众所周知,C++并不是一种流行的Web开发语言,究其原因有很多:语言门槛高、使用难度大、开发效率低......话虽如此,​​但随着Emscripten的成熟,未来C++在Web方面会发挥......
  • C#多线程(四)并行编程篇之结构化
    前言在前三章中我们的案例大量使用到了Thread这个类,通过其原始API,对其进行创建、启动、中断、中断、终止、取消以及异常处理,这样的写法不仅不够优雅(对接下来这篇,我称其为.......
  • C++进阶知识汇总
    知识来源:https://www.imooc.com/learn/1305二进制在计算机中的意义:计算机如何存负整数:原码:符号位变为1反码:除符号位其余取反补码:=反码+1是-7的表示方法,计算机用补码......
  • Guide to predefined macros in C++ compilers (gcc, clang, msvc etc.)
    https://blog.kowalczyk.info/article/j/guide-to-predefined-macros-in-c-compilers-gcc-clang-msvc-etc..htmlWhenwritingportableC++codeyouneedtowritecondi......
  • C++学习---cstdio的源码学习分析08-重新打开文件流函数freopen
    cstdio中的文件访问函数stdio.h中定义了一系列文件访问函数(fopen,fclose,fflush,freopen,setbuf,setvbuf),接下来我们一起来分析一下freopen对应的源码实现。-fopen:打开文件-......
  • 多线程(概念)
    一概述是指从软件或者硬件上实现 多个线程 并发执行的技术具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。当数据量非常庞大的时候,为了提......
  • 多线程的实现 *
    一继承Thread类实现步骤:定义一个类(MyThread)继承Thread类在MyThread类中重写run()方法创建MyThread类的对象启动线程(对象名.start();)1run()和start()方法的区别(1)......
  • 「REMAKE C++」Day 3
    Day3完成了C++Primer第4,5章的阅读常量迭代器,不能修改其所指向的对象,可以移动它,vector<int>::const_iteratorit=v.begin();,常量容器只有常量迭代器。标......