如果线程修改的变量标记为volatile,为什么false共享问题

时间:2015-07-02 11:21:49

标签: java multithreading caching

我一直在看Martin Thompson的文章。这是对虚假分享的解释。

http://mechanical-sympathy.blogspot.co.uk/2011/07/false-sharing.html

    public final class FalseSharing
    implements Runnable
    {
        public final static int NUM_THREADS = 4; // change
        public final static long ITERATIONS = 500L * 1000L * 1000L;
        private final int arrayIndex;

        private static VolatileLong[] longs = new VolatileLong[NUM_THREADS];


        static
        {    
            for (int i = 0; i < longs.length; i++)
            {
                longs[i] = new VolatileLong();
            }
        }

        public FalseSharing(final int arrayIndex)
        {
            this.arrayIndex = arrayIndex;
        }

        public static void main(final String[] args) throws Exception
        {
            final long start = System.nanoTime();
            runTest();
            System.out.println("duration = " + (System.nanoTime() -start));
        }

        private static void runTest() throws InterruptedException
        {
            Thread[] threads = new Thread[NUM_THREADS];

            for (int i = 0; i < threads.length; i++)
            {
                threads[i] = new Thread(new FalseSharing(i));
            }

            for (Thread t : threads)
            {
                t.start();
            }

            for (Thread t : threads)
            {
                t.join();
            }
        }

        public void run()
        {
            long i = ITERATIONS + 1;
            while (0 != --i)
            {
                longs[arrayIndex].value = i;
            }
        }

        public final static class VolatileLong
        {
            public volatile long value = 0L;
            public long p1, p2, p3, p4, p5, p6; // comment out
        }
    }

该示例演示了多个线程使彼此的缓存行无效所经历的速度减慢,即使每个线程仅仅更新一个变量。

  

Blockq图1.上面说明了错误共享的问题。在核心1上运行的线程想要更​​新变量X,而核心2上的线程想要更​​新变量Y.不幸的是,这两个热变量位于同一缓存行中。每个线程都将竞争缓存行的所有权,以便他们可以更新它。如果核心1获得所有权,那么缓存子系统将需要使核心2的相应缓存行无效。当Core 2获得所有权并执行其更新时,将告知核心1使其缓存行的副本无效。这将通过L3缓存来回乒乓,极大地影响性能。如果竞争核心位于不同的套接字上并且还必须跨越套接字互连,则问题将进一步加剧。

我的问题如下。如果要更新的所有变量都是易失性的,为什么这个填充会导致性能提升?我的理解是,volatile变量总是写入并读取到主存储器。因此,我假设在此示例中对任何变量的每次写入和读取都将导致当前核心高速缓存行的刷新。

所以根据我的理解。如果线程1使线程2的高速缓存行无效,那么在它从其自己的高速缓存行读取值之前,这将不会成为线程2的特权。它读取的值是一个易失性值,因此这有效地使缓存变脏,从而导致从主存储器读取。

我的理解在哪里出错了?

由于

1 个答案:

答案 0 :(得分:5)

  

如果要更新的所有变量都是易失性的,为什么这个填充会导致性能提升?

所以这里有两件事:

  1. 我们正在处理一组VolatileLong个对象,每个线程都在自己的VolatileLong上工作。 (见private final int arrayIndex)。
  2. 每个VolatileLong对象都有一个volatile字段。
  3. volatile访问意味着线程必须使保留其volatile long value的{​​{3}}无效,并且需要锁定该缓存行以更新它。正如文章所述,缓存行通常约为64字节左右。

    文章说通过向VolatileLong对象添加填充,它将每个线程锁定的对象移动到不同的缓存行。因此,即使不同的线程在分配volatile long value时仍然跨越内存障碍,它们也处于不同的缓存行中,因此不会导致过多的L2缓存带宽。

    总之,性能提升的原因在于,即使线程仍然锁定其缓存行以更新volatile字段,这些锁现在位于不同的内存块上,因此它们不会与其他线程冲突锁定并导致缓存失效。