问题

#include<iostream>
#include<thread>
int main()
{
int sum = 0;
auto f = [&sum]() {
for (int i = 0; i < 10000; i++)
sum += 1;
};
std::thread t1(f);
std::thread t2(f); t1.join();
t2.join();
std::cout << "the sum of 2 threads is: " << sum << std::endl;
std::cin.get();
return 0;
}

这个程序只是简单的通过两个线程对同一个变量进行累加10000次,正常不管线程执行的先后顺序,结果都应该是20000才对,可实际输出结果如图所示,程序的输出3次的结果都不一样,不一定是预期的20000;

分析

对于+1操作,具体执行可以分为3个操作,如下图所示:

可以看出问题发生在两个线程写的时候,如线程1刚写完,线程2继续写,则丢失一次加法。所以得出的值往往小于20000。

解决

可以通过std::mutex加锁对变量操作进行保护,有没有不用锁也能实现的呢?C++中提供了原子操作可以实现这一目标。

代码如下:

	std::atomic<int> sum1 = 0;
auto f1 = [&sum1]() {
for (int i = 0; i < 10000; i++)
sum1+=1;
}; std::thread t3(f1);
std::thread t4(f1); t3.join();
t4.join();
std::cout << "the sum of 2 threads with atomic is: " << sum1 << std::endl;

输出如下:



可以看出未原子化的sum仍然是每次结果不尽相同,而原子化的sum1每次结果都为20000。

所谓原子操作指的是不可分割的操作,可以理解为只能编译成一条单独的CPU执行指令,不可以再分解,C++中,基本通过原子类型来实现原子操作。这种原子类型为std::atomic<T>,其中模板参数T为基本的数据类型,如bool,char,int,指针等。

程序中将sum1原子化,并调用+=操作符(已重载为原子操作),之前分解的3步成了不可分割的1步,所以不会出现两个线程同时已经进入写的状态,进而能保证累加结果的正确。

注意事项

  1. 若累加操作改为sum1=sum1+1,就不是原子操作了,结果与sum没有差别

  2. int型++/+=是原子操作fetch_add()的重载,类似的还有fetch_sub()/fetch_and/fetch_or()/fetch_xor()

C++原子操作与内存序 1的更多相关文章

  1. C++11的原子量与内存序浅析

    一.多线程下共享变量的问题 在多线程编程中经常需要在不同线程之间共享一些变量,然而对于共享变量操作却经常造成一些莫名奇妙的错误,除非老老实实加锁对访问保护,否则经常出现一些(看起来)匪夷所思的情况.比 ...

  2. Linux并发与同步专题 (1)原子操作和内存屏障

    关键词:. <Linux并发与同步专题 (1)原子操作和内存屏障> <Linux并发与同步专题 (2)spinlock> <Linux并发与同步专题 (3) 信号量> ...

  3. 一文读懂原子操作、内存屏障、锁(偏向锁、轻量级锁、重量级锁、自旋锁)、Disruptor、Go Context之上半部分

    我不想卷,我是被逼的 在做了几年前端之后,发现互联网行情比想象的差,不如赶紧学点后端知识,被裁之后也可接个私活不至于饿死.学习两周Go,如盲人摸象般不知重点,那么重点谁知道呢?肯定是使用Go的后端工程 ...

  4. linux内核同步之每CPU变量、原子操作、内存屏障、自旋锁【转】

    转自:http://blog.csdn.net/goodluckwhh/article/details/9005585 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[-] 一每 ...

  5. memory barrier 内存屏障 编译器导致的乱序

    小结: 1. 很多时候,编译器和 CPU 引起内存乱序访问不会带来什么问题,但一些特殊情况下,程序逻辑的正确性依赖于内存访问顺序,这时候内存乱序访问会带来逻辑上的错误, 2. https://gith ...

  6. C++11 并发指南七(C++11 内存模型一:介绍)

    第六章主要介绍了 C++11 中的原子类型及其相关的API,原子类型的大多数 API 都需要程序员提供一个 std::memory_order(可译为内存序,访存顺序) 的枚举类型值作为参数,比如:a ...

  7. LINUX内核内存屏障

    =================                          LINUX内核内存屏障                          ================= By ...

  8. C++11原子操作与无锁编程(转)

    不讲语言特性,只从工程角度出发,个人觉得C++标准委员会在C++11中对多线程库的引入是有史以来做得最人道的一件事:今天我将就C++11多线程中的atomic原子操作展开讨论:比较互斥锁,自旋锁(sp ...

  9. 内存屏障在CPU、JVM、JDK中的实现

    前言 内存屏障(英语:Memory barrier),也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,它使得 CPU 或编译器在对内存进行操作的时候, 严格按照一定的顺序来执行, 也就是说在内 ...

  10. C++ 内存模型 write_x_read_y 试例构造

    之前一段时间偶然在 B 站上刷到了南京大学蒋炎岩(jyy)老师在直播操作系统网课.点进直播间看了一下发现这个老师实力非凡,上课从不照本宣科,而且旁征博引又不吝于亲自动手演示,于是点了关注.后来开始看其 ...

随机推荐

  1. Java中的包装类(wrapper class)

    1.介绍 顾名思义,包装类是封装Java原始类型的对象.每个Java原始类型都有一个对应的包装类: 基本类型 包装类 boolean Boolean byte Byte short Short cha ...

  2. spring boot中配置网页语言国际化

    项目地址:https://gitee.com/indexman/spring_boot_in_action 开发步骤 1.编写国际化配置文件 场景是给登录页面 login.html添加国际化支持. 2 ...

  3. 溯源反制-Mysql蜜罐

    东西比较老,类似的文章网上已经很多,原理主要是通过服务端的load data动作可以主动向客户端获取文件. 看过hfish等自带的mysql蜜罐读取/etc/passwd,感觉还差点实用性.这次文章主 ...

  4. Innodb之事务

    目录 一.事务基本概念 事务的特性:ACID 事务类型 1.扁平事务 2.带保存点的扁平事务 3.链式事务 4.嵌套事务 5.分布式事务 二.事务的实现概述 三.redo log 1)组成 2)red ...

  5. 我的小程序之旅九:微信开放平台unionId机制介绍

    一.机制说明 参考文档:https://developers.weixin.qq.com/minigame/dev/guide/open-ability/union-id.html 如果开发者拥有多个 ...

  6. Redis原理再学习02:数据结构-动态字符串sds

    Redis原理再学习:动态字符串sds 字符 字符就是英文里的一个一个英文字母,比如:a.中文里的单个汉字,比如:好. 字符串就是多个字母或多个汉字组成,比如字符串:redis,中文字符串:你好吗. ...

  7. 在Winform界面中使用自定义控件,丰富界面的效果处理

    我们在<SqlSugar开发框架>中,Winform界面开发部分往往也用到了自定义的用户控件,对应一些特殊的界面或者常用到的一些局部界面内容,我们可以使用自定义的用户控件来提高界面的统一性 ...

  8. 如何创建自己的Spring Boot Starter并为其编写单元测试

    当我们想要封装一些自定义功能给别人使用的时候,创建Spring Boot Starter的形式是最好的实现方式.如果您还不会构建自己的Spring Boot Starter的话,本文将带你一起创建一个 ...

  9. 【Azure Redis 缓存】对于Azure Redis 从 Redis 4 升级到 Redis 6 的一些疑问

    问题描述 使用Azure Redis服务,客户端使用Redisson 3.X , 在近期Microsoft Azure对Redis服务进行大规模变动升级( Redis 版本由 4 升级到 6),对于这 ...

  10. 【Azure Developer】Java代码访问Key Vault Secret时候的认证问题,使用 DefaultAzureCredentialBuilder 或者 ClientSecretCredentialBuilder

    问题描述 使用Java SDK获取Key Vault Secret机密信息时,需要获取授权.通常是使用AAD的注册应用(Client ID, Tenant ID, Client Secret)来获取  ...