zoukankan      html  css  js  c++  java
  • 关于自增加

    int i=0;

    i++;

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

  • 相关阅读:
    python常用函数总结 分类: python基础学习 2014-02-07 14:12 260人阅读 评论(0) 收藏
    classmethod类方法 分类: python 小练习 python基础学习 2014-02-07 10:36 214人阅读 评论(0) 收藏
    Python模块功能在实际应用方案中的相关功能的介绍 分类: database 2014-01-29 16:37 261人阅读 评论(0) 收藏
    Python中使用MySQLdb连接MySQL 分类: database 2014-01-29 15:37 358人阅读 评论(0) 收藏
    Ubuntu下彻底卸载mysql、安装mysql、及MySQLdb模块 分类: ubuntu 问题总结 database 2014-01-23 13:49 2560人阅读 评论(0) 收藏
    在Ubuntu上安装MySQLdb 分类: database 问题总结 ubuntu 2014-01-23 10:58 771人阅读 评论(0) 收藏
    Ubuntu 12.04 安装mysql及mysql-python 分类: python Module ubuntu database 2014-01-23 10:07 615人阅读 评论(0) 收藏
    Reading package lists... Error! 解决方案 分类: ubuntu database 问题总结 2014-01-23 09:45 1546人阅读 评论(0) 收藏
    shell技巧之多行改写成一行 分类: ubuntu 2014-01-22 18:26 268人阅读 评论(0) 收藏
    50. Tomcat 集群部署
  • 原文地址:https://www.cnblogs.com/taek/p/5820103.html
Copyright © 2011-2022 走看看