• 关于自增加


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

  • 相关阅读:
    DATAGUARD物理standby角色切换(笔记二)
    深入了解start with .....connect by (一)
    oracle lob字段处理
    InstallShield12命令行模式编译工程的几点问题总结Emma友情赞助转帖
    Installshield实用小函数为字符串截掉头尾空格
    使用Java Service Wrapper将Java程序发布成Windows Service艾泽拉斯之海洋女神出品
    Kevin专栏自定义安装对话框的界面
    Kevin专栏如何制作试用版安装包
    【海洋女神原创】关于installshield“完美卸载”的改进方法
    Installshield的相对路径问题
  • 原文地址:https://www.cnblogs.com/taek/p/5820103.html
Copyright © 2020-2023  润新知