zoukankan      html  css  js  c++  java
  • 原子操作

    今天看到文章讨论 i++ 是不是原子操作。

    答案是不是!

    参考:http://blog.csdn.net/yeyuangen/article/details/19612795

    1.i++ 不是,分为三个阶段:

    内存到寄存器
    寄存器自增
    写回内存
    这三个阶段中间都可以被中断分离开.

     

    2.++i首先要看编译器是怎么编译的

    某些编译器比如VC在非优化版本中会编译为以下汇编代码:

    __asm
    {
            moveax,  dword ptr[i]
            inc eax
            mov dwordptr[i], eax
    }
    这种情况下,必定不是原子操作,不加锁互斥是不行的。
    假设加了优化参数,那么是否一定会编译为“inc dword ptr[i]”呢?答案是否定的,这要看编译器心情,如果++i的结果还要被使用的话,那么一定不会被编译为“inc dword ptr[i]”的形式。
    那么假设如果编译成了“inc dword ptr[i]”,这是原子操作,是否就不需要加锁了呢?如果在单核机器上,不加锁不会有问题,但到了多核机器上,这个不加锁同样会带来严重后果,两个CPU可以同时执行inc指令,但是两个执行以后,却可能出现只自加了一次。
    真正可以确保不“额外”加锁的汇编指令是“lock inc dword ptr[i]”,lock前缀可以暂时锁住总线,这时候其他CPU是无法访问相应数据的。但是目前没有任何一个编译器会将++int编译为这种形式。

     

    怎么证明 i++ 不是原子操作,可以用下面的代码:

    import java.util.concurrent.*;
    
    /**
     * Created by chenghao on 15/9/30.
     */
    public class TestPP implements Runnable{
    
        private static int i = 0;
    
        private static CountDownLatch countDownLatch = new CountDownLatch(10);
    
        public static void main(String[] args) throws InterruptedException {
    
            ExecutorService executorService = Executors.newFixedThreadPool(10);
            for(int i = 0;i<10;i++){
                TestPP pPer = new TestPP();
                executorService.execute(pPer);
            }
            countDownLatch.await(300000, TimeUnit.MILLISECONDS);
            System.out.println(i);
    
        }
    
        public void run() {
            for(int j=0;j<10000;j++){
                i++;
            }
            System.out.println(Thread.currentThread().getName()+" ++ end");
            countDownLatch.countDown();
        }
    }

    得到结果:

    输出:
    pool-1-thread-1 ++ end
    pool-1-thread-4 ++ end
    pool-1-thread-2 ++ end
    pool-1-thread-5 ++ end
    pool-1-thread-3 ++ end
    pool-1-thread-6 ++ end
    pool-1-thread-7 ++ end
    pool-1-thread-8 ++ end
    pool-1-thread-9 ++ end
    pool-1-thread-10 ++ end
    47710
    
    可以看出每个线程都完成了,但总和小于原子操作的预期。

    那么哪些操作是原子操作呢,最好的方法,就是看汇编,看是否编译成一行的指令。

    另外,常见的原子操作可以见如下:http://www.2cto.com/kf/201512/453978.html

    1 处理器支持的一系列原子操作

    1.1 CAS(Compare And Swap/Set)

    int compare_and_swap(int* reg, int oldval, int newval) {
    ...
    }

    1.2 Fetch And Add

    在某个内存地址存储的值上增加一个值, 下面是段伪代码:

    function FetchAndAdd(address location, int inc) {
        int value := *location
        *location := value + inc
        return value
    }

    1.3 Test And Set

    写新值入内存地址,并返回内存地址之前存放的值, 这可以通过spin技术实现lock函数. 伪码如下:

    function TestAndSet(boolean_ref lock) {
        boolean initial = lock
        lock = true
        return initial
    }

    感觉这个test没有起到分支的作用,而仅仅是返回原值。

    另外,信号量的操作都是原子性的。

  • 相关阅读:
    高等代数中的名词解析No1
    概率论中的名词解释(个人理解,非官方) No1
    概率论中的公式解释(个人理解,非官方) No1
    CentOS7 网络设置
    神经网络与人工智能No0导言(笔记版)
    centos7 防火墙设置
    神经网络与人工智能No1Rosenblatt感知器(笔记版)
    输入法打不出来的数学符号大全
    php对xml文件的解析
    PHPExcel生成Excel模版
  • 原文地址:https://www.cnblogs.com/charlesblc/p/6142443.html
Copyright © 2011-2022 走看看