关于自增加

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中准备去读内存和真正读内存的间隔很小的,

 

posted @ 2016-08-29 23:10  taek  阅读(300)  评论(0编辑  收藏  举报