单线程读单线程写一个变量是否需要加锁,刚毕业的时候我会有这样的想法:一个线程只读并没有改变变量的值并不会有两个线程同时写一个变量产生竞态,所以不用加锁,但是工作中长者给我指导都是多线程必须加锁,所以我也没有深究这个问题,从来没有想过为什么。

  过了一段时间后,了解到原子性这个概念,了解到虽然一个线程读一个线程写,但是因为对一个线程的写和读并非是原子的,读线程可能读到另外一个线程写到一半的值,所以要加锁来保护。但是上面的答案直到我看到下面的文章再一次被推翻了。

  1分析很透彻的C/C++ 基本类型及是否需要多线程锁

  2关于C++多线程程序中简单类型(int/bool)的安全性

  3关于int全区变量读写的原子性

  关于这个观点我也写了个测试发现结果是正确的(因为我测试环境是64位所以char,int,long读写都是原子的)


//  Created by 杜国超 on 19/8/25.
//  Copyright © 2019年 杜国超. All rights reserved.
//#include <memory>
#include <iostream>
#include <thread>
#include <atomic>
#include <zconf.h>
#include <vector>
#include <algorithm>using namespace std;char c_v = 0;
int i_v = 0;
long long l_v = 0;
float f_v = 0.0f;std::atomic_bool flag(true);void thread_func1() {while (flag){c_v = '1';i_v = 111111111;l_v = 111111111;f_v = 1111.11111;usleep(1);}
}void thread_func2() {while (flag){c_v = '2';i_v = 222222222;l_v = 222222222;f_v = 2222.22222;usleep(1);}
}void thread_func3() {while (flag){char tmpc = c_v;if(!(tmpc == '1' || tmpc == '2')){printf("char value read error %c\n",tmpc);}int tmpi = i_v;if(!(tmpi == 111111111 || tmpi == 222222222)){printf("int value read error %d\n",tmpi);}long tmpl = l_v;if(!(tmpl == 111111111 || tmpl == 222222222)){printf("long value read error %ld\n",tmpl);}float tmpf = f_v;if(!(tmpf == 1111.11111 || tmpf == 2222.22222)){printf("float value read error %f\n",tmpf);}usleep(1);}
}int main() {std::vector<thread> threads;for(int i = 0 ;i < 50 ; i++){threads.push_back(std::move(std::thread(thread_func1)));}for(int i = 0 ;i < 50 ; i++){threads.push_back(std::move(std::thread(thread_func2)));}std::thread check_thread(thread_func3);std::for_each(threads.begin(),threads.end(),[](std::thread& th) {th.join();});check_thread.join();return 0;
}

  其实对于上面的问题有一个很好的解释就是:锁本身也是一个基本类型,如果锁的读写不是原子的,那谁来保证锁的线程安全,给锁再加个锁吗。

  这样新的疑问诞生了,既然char,bool,int,读写是原子的,为什么也要加锁。关于这个问题又很多个关键点,首先是仍然是原子性问题比如a++,这个操作并非原子的如果两个线程同时进行这个操作可能会导致操作的丢失,所以如果多线程写没有争论必须要加锁或者使用atomic相关的操作来保证线程的安全性。不过我们这里讨论的是单线程读单线程写是否要加锁,对于非上面提到的基本了类型由于原子性的限制必须保证不能让多线程读到写线程写到一半的值所以必须要加锁。

  保证了原子性后还有一个可见性问题,写线程改变了变量的值读线程不一定能够立马读到改变后的值,但是如果我们可以接受这种延时读取完全可以不做任何额外的处理(比如我们一个值表示当前的池子中的水位,我们写线程加水后把表示水位的值改变为新的状态我们读线程取水,这次取不到可以下次取),当然这个问题也可以通过voliate关键字来解决,最暴力的手段当然还是加锁。在我们可以保证了可见性之后还有一个比较头疼的问题就是cpu执行时的乱序问题,cpu乱序执行分为两个维度。第一个维度的乱序是对于不相干的计算乱序执行。譬如

MUL R1, R2
ADD R3, R4

乘法比加法需要更多的cycle,而这两个指令操作的寄存器没有任何依赖关系,所以可以乱序执行,不必等待MUL执行完以后再执行ADD。第二个维度是memory reordering。具体什么样的规则取决于硬件的内存模型。Intel x64的基础是一种叫做total store ordering(TSO)的模型,大概可以理解为,除了store-load可以reorder以外,其他的都不可以。就是说如果一个store先执行了然后是一个load,他们操作的地址也不同,就可以reorder。这样设计的目的主要是为了掩盖store的latency。具体的实现则是在CPU和缓存之间加一个store buffer(揭开内存屏障的面纱),store发生以后被放到store buffer,从store buffer出来以后才能对其他核可见(visible),来保证,最暴力的手段当然还是加锁。如果你并没有类似的依赖逻辑就是单纯的一个线程读,一个线程写,对于上面提到几一些基本变量你完全可以不做任何处理更不需要加锁。

单线程读单线程写一个变量是否一定要加锁相关推荐

  1. 自己动手写一个 SimpleVue

    最近看到一句话很有感触 -- 有人问 35 岁之后你还会在写代码吗?各种中年程序员的言论充斥的耳朵,好像中年就不该写代码了,但是我想说,若干年以后,有人问你闲来无事你会干什么,我想我会说,写代码,我想 ...

  2. SQL允许你用EXECUTE执行一个变量中定义的SQL语句,并且允许你在被执行的SQL语句中,再次嵌套入一个变量定义的语句,并且再次在其中用EXECUTE执行它...

    declare @sqlstr varchar(3000) set @sqlstr='declare @subsqlstr varchar(1000);' set @sqlstr=@sqlstr+'s ...

  3. 文件对比8,单线程读,多线程对比,对比进度条,对比结果导出excel文件,已验收

    文件对比8,单线程读,多线程对比,对比进度条,对比结果导出excel文件,已验收 介绍 项目结构预览 base util CompareThreadUtil ExcelUtil Md5 ReadFil ...

  4. 进程P1、P2、P3共享一个表格F,P1对F只读不写,P2对F只写不读,P3对F先读后写。进程可同时读F,但有进程写时,其他进程不能读和写。

    进程P1.P2.P3共享一个表格F,P1对F只读不写,P2对F只写不读,P3对F先读后写.进程可同时读F,但有进程写时,其他进程不能读和写.要求:(1)正常运行时不能产生死锁.(2)F的并发度要高. ...

  5. Wincc中使用使用VB脚本进行变量的读和写

    问题详情 项目中需要实现通过Wincc输入输出域来改变海康威视的视频服务器IP.用户名.密码等参数的输入.实现的思路是建立输入输出域并新建内部变量,将输入输出域与内部变量关联,再新建一个按钮,并在按钮 ...

  6. a,b为2个整型变量,在不引入第三个变量的前提下写一个算法实现 a与b的值互换...

    package com.Summer_0424.cn;/*** @author Summer* a,b为2个整型变量,在不引入第三个变量的前提下写一个算法实现 a与b的值互换?*/ public cl ...

  7. 写一个使两个整数进行交换的方法(不能使用临时变量) 【前端每日一题-27】...

    写一个使两个整数进行交换的方法(不能使用临时变量) 这道题是一个比较有意思的题,记录于此. var a=10; var b=20;...不用临时变量让a和b交换console.log(a); cons ...

  8. zookeeper集群,每个服务器上的数据是相同的,每一个服务器均可以对外提供读和写的服务,这点和redis是相同的,即对客户端来讲每个服务器都是平等的。...

    zookeeper集群,每个服务器上的数据是相同的,每一个服务器均可以对外提供读和写的服务,这点和redis是相同的,即对客户端来讲每个服务器都是平等的.

  9. CPU读/写一个存储单元

    CPU读/写一个存储单元 先将地址 ⇒\Rightarrow⇒ 地址总线 高位地址 ⇒\Rightarrow⇒ 译码后产生片选信号 低位地址 ⇒\Rightarrow⇒ 存储器 ⇒\Rightarro ...

最新文章

  1. 报名 | 2019年第六届清华大学大数据社会科学讲习班
  2. Tree的实现,js开发组件dtree
  3. 从0实现一个tinyredux
  4. html5创建对象的方法,JavaScript面向对象-使用工厂方法和构造函数方法创建对象...
  5. windows复制文件路径_如何在Windows 10上复制文件的完整路径
  6. 怎样解决MySQL数据库主从复制延迟的问题?
  7. Android 物联网 传感器
  8. 在武大吉奥期间的任务
  9. python自训练神经网络_tensorflow学习笔记之简单的神经网络训练和测试
  10. linux 内核学习线索初步
  11. python nonlocal的用法_python global和nonlocal用法解析
  12. 前瞻设计:创新型战略推动可持续变革(全彩)
  13. php评论倒序 zblog_ZblogPHP调用最新、评论最多、浏览最多、置顶文章
  14. swagger 常用注解
  15. ★★★5230打字慢的解决方法...绝对有用...只需要在手机上轻微的设置一下(转)...
  16. Acer宏碁笔记本触摸板失效解决方法
  17. 医学图像处理——基本概念(色彩、直方图、CT值)
  18. 从零到一,全套搜狗收录教程分享
  19. css案例17——圆角头像
  20. 【Git】回退 commit 版本详解

热门文章

  1. Java自定义异常类以及异常拦截器
  2. 批量修改文件名-批处理(三)
  3. origin 三维坐标中 多条曲线 waterfall 瀑布图
  4. 2019年秋招 Java 面试知识点梳理(高频问题)
  5. 基于 gma 的栅格数据格式转换:以netCDF(.nc)与GTiff(.tif)文件互转为例
  6. CAD 电气版 元件包含中文的描述显示问号
  7. 团队管理8——人才成长计划
  8. 【转】adb sideload 刷机模式. 一种用USB线,刷卡刷包的方法.
  9. elf中的bss data
  10. 【达梦8数据库百度网盘下载地址】