关于自增加

int i=0;

i++;

printf("%d\n",i);

对于单CPU,开两个线程的话,如果不使用锁 最终的i可能不是2

对应的汇编是

1 movl $0 i(%rip)

2 movl i(%rip) %eax

3 addl $1 %eax

4 movl %eax i(%rip)

可见i++ 不是原子的

线程有属于自己的CPU指令和寄存器,但内存是共享的,

可理解为A,B为房客,每人搬进去之前,上一个人的东西要搬出来,腾出地方,再给下一个住, 线程也这样

线程1执行下3的时候,由于时间片的原因, 线程1被换出,CPU中寄存器的内容(1)被清0,线程2进入CPU,一直执行到4,i值为1,线程2 由于时间片,被换出,线程进入cpu,CPU的寄存器内容还是1,写入内存,最终i值为1

线程1                                                                       线程2

1 movl $0 i(%rip)

2 movl i(%rip) %eax

3 addl $1 %eax

(由于时间片,被换出,同时CPU相应寄存器内容保存其他地方,同时清0)

                                      

                                      1 movl $0 i(%rip)

                                       2 movl i(%rip) %eax

                                       3 addl $1 %eax

                                                        4 movl %eax i(%rip)    //此时i为2

4 movl %eax i(%rip) //线程1获得CPU,将之前保存到别的地方的寄存器内容放回去,再写回内存,但i为1

这时要加锁了

1 movl $0 i(%rip)

线程1                            线程

lock

2 movl i(%rip) %eax

3 addl $1 %eax

4 movl %eax i(%rip)

unlock                               

                              lock                             

                              2 movl i(%rip) %eax

                              3 addl $1 %eax

                              4 movl %eax i(%rip)

                              unlock   

   

假设线程1先获得了锁,那么 把变量i所在内存的数据 load到寄存器,再对寄存器数据加1,再将结果写回变量i所在内容中 ,此时i为1

在上面任何一个步骤终,线程2去尝试加锁将失败

当线程1解锁后,线程去加锁,还是重复上面的步骤,最终i为2

虽然线程之间加锁能保证数据的原子性,但耗性能,线程切换也就是清空寄存器,清除缓存,但对于单CPU来说也只能这样了  可使用GCC本身支持的原子函数

若是多CPU呢

线程1指定到CPU1上,线程指定到CPU2上, 这时就没有加锁的必要性了,但若执行上面的程序,i 的值,还是有可能不为2

如果说单CPU中不为2,是由于线程切换,寄存器清0产生的,那么多CPU中i不为2,就是由于CPU之间的缓存不一致产生的

我们知道CPU不会直接跟内存打交道的,CPU是直接跟缓存打交道的

对于int i=0对应的汇编 movl $0 i(%rip) cpu拿到这个指令后,发现其内存地址不在缓存中,就去内存中load 大小为64字节的数据(cpu缓存局部性)

                    cpu1                 cpu2

(1) int i=0        缓存 0                缓存0

(2) i++            缓存  1               缓存1

为了让CPU之间通信,需要对bus加锁,也就是锁住内存, 当cpu1执行i++ 时,锁总线,这时cpu2是能感知到的,它会意思到它缓存中相应数据非法,等待

当cpu对bus解锁后,cpu2也是能感知到的,再从内存中load相应数据到cpu2的缓存中,再进行计算,这时i的值为2了

对bus加锁,会使得其他cpu不能使用内存,即读取不相关的数据也不行,性能差

就改为锁缓存,不再锁总线了

缓存一致性 MESI

当CPU1执行到 (2)时, 发一个指令给CPU2,告诉它,它的缓存数据是invalid,CPU1需要重新读内存 , 同时CPU1将i设置为exclusive,进行i++后,修改状态为modify

当CPU2打算从内存读i时,CPU1能感知到的,马上将 i的状态 置为share,并写回内存,CPU2从内存读取到i的值,已经为1了,再i++,最终i的值为2

不管是锁总线,还是锁缓存,都是CPU内部提供的指令,比软件锁要快多了

cpu内部提供的锁 在gcc中对应的函数是xchg , cmpxchg(CAS)

cpu 1                                                                 cpu2

i++       发出指令,设置i状态为exlusive                             收到指令后,意识到自己缓存区中的数据失效invalid

同时执行i++,将状态置为modify                             马上去读取内存

感觉到cpu2要读取内存,设置状态为

share,并写回内存

                            读内存

cpu2中准备去读内存和真正读内存的间隔很小的,

时间: 2024-08-28 15:10:09

关于自增加的相关文章

SqlServer给一个表增加多个字段语法

添加字段语法 alter table table_name add column_name +字段类型+ 约束条件 给一个表增加多个字段: use NatureData go alter table XunHu add MaleCount varchar(50) null, FemaleCount varchar(50) null, SubadultCount varchar(50) null, LarvaeCount varchar(50) null, TraceType varchar(50

Oracle 增加修改删除字段

添加字段的语法:alter table tablename add (column datatype [default value][null/not null],-.); 修改字段的语法:alter table tablename modify (column datatype [default value][null/not null],-.); 删除字段的语法:alter table tablename drop (column); 添加.修改.删除多列的话,用逗号隔开. 使用alter

SPRING IN ACTION 第4版笔记-第四章ASPECT-ORIENTED SPRING-010-Introduction为类增加新方法

一. 1.Introduction的作用是给类动态的增加方法 When Spring discovers a bean annotated with @Aspect , it will automatically create a proxy that delegates calls to either the proxied bean or to the introduction implementation, depending on whether the method called be

增加一个类似于成功跳转和错误跳转的跳转

第一步,找到成功跳转函数定义的地方,增加info()函数 第二,找到dispatchJump(),修改成 第三步:找到convention.php配置文件,增加一条配置模板 第四.调用 注意:模板文件要使用图片要用绝对路径.第二步判断是否等于1,不可省略,因为非0就是true

数据库——基础(数据库操作,表格操作)——增加高级查询

笔记 LAMP:Linx(操作系统) A(阿帕奇)——网页的应用程序 M(Mysql):体积小,应用简单 P(PHP) 第一步:搭建网页环境——A\M\P WAMP:用WAMP搭建环境 DW:更好的显示 数据库的基本操作: 数据库——表结构——字段(列) 每一行数据成为一条数据(记录) 特点:关系型数据库,有严格的规范 1.必须有主键:能够唯一标识一条数据的字段 2 T-SQL:通用的数据库操作语句 自增长列code(主键列) ;连接键表 最后一个字段不加 ,#注释 创建表:create tab

新建表和增加外键

当我们创建好了一个数据库后,第一件事要做的就是给这个数据库增加表. Create Table tblPerson ( Id INT NOT NULL PRIMARY KEY, Name NVARCHAR(50) NOT NULL, Email NVARCHAR(50) NOT NULL, GenderId INT ) Create Table tblGender ( Id INT NOT NULL PRIMARY KEY, Gender NVARCHAR(20) ) 上面的代码创建了tblPer

IOS -->> 给UITextView增加链接

现在在iOS添加你自己的Twitter账户更加简单了,现在你可以给一个NSAttributedString增加链接了,然后当它被点击的时候唤起一个定制的action. 首先,创建一个NSAttributedString然后增加给它增加一个NSLinkAttributeName 属性,见以下: NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"This

项目启动时增加日志与系统配置

在web.xml中增加Servlet:log4j <servlet> <servlet-name>log4j</servlet-name> <servlet-class>com.wafer.weixin.servlet.Log4JServlet</servlet-class> <load-on-startup>1</load-on-startup>//加载该servlet,1代表优先级 </servlet> &

安卓给button增加点击事件

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.mmmm); //获取XML里面的button Button button=(Button)findViewById(R.id.button1); //给button增加点击事件 button.setOnClickListener(new OnClick

VMware虚拟机Ubuntu增加硬盘空间

Android4.1源码出来,没事也想下载下来看一下,结果悲剧的发现虚拟机上给Ubuntu分配的硬盘空间太小了,就剩下2.7G.而在官方文档中:The source download is approximately 6GB in size,说明源代码大小在6G左右,只好为Vmware虚拟机中的ubuntu再挂载一个20G的硬盘. (1)  增加虚拟机硬盘. 首先,打开Vmware里面VM里面的setting.如下图: 进入设置界面: 选择Hard Disk,点击下面Add: 点击next:选择