Thread.MemoryBarrier和一个简单属性的锁定差异

时间:2014-03-17 14:49:29

标签: c# .net multithreading

对于以下情况,在使用MemoryBarrier

之间的线程安全性,结果和性能方面存在差异
private SomeType field;

public SomeType Property
{
    get
    {
        Thread.MemoryBarrier();
        SomeType result = field;
        Thread.MemoryBarrier();
        return result;
    }
    set
    {
        Thread.MemoryBarrier();
        field = value;
        Thread.MemoryBarrier();
    }
}

lock声明(Monitor.EnterMonitor.Exit

private SomeType field;
private readonly object syncLock = new object();

public SomeType Property
{
    get
    {
        lock (syncLock)
        {
            return field;
        }
    }
    set
    {
        lock (syncLock)
        {
            field = value;
        }
    }
}

因为引用赋值是原子的所以我认为在这种情况下我们确实需要任何锁定机制。

性能 MemeoryBarrier比Release的锁实现快约2倍。以下是我的测试结果:

Lock
Normaly: 5397 ms
Passed as interface: 5431 ms

Double Barrier
Normaly: 2786 ms
Passed as interface: 3754 ms

volatile
Normaly: 250 ms
Passed as interface: 668 ms

Volatile Read/Write
Normaly: 253 ms
Passed as interface: 697 ms

ReaderWriterLockSlim
Normaly: 9272 ms
Passed as interface: 10040 ms

Single Barrier: freshness of Property
Normaly: 1491 ms
Passed as interface: 2510 ms

Single Barrier: other not reodering
Normaly: 1477 ms
Passed as interface: 2275 ms

以下是我在LINQPad中测试它的方法(在Preferences中设置优化):

void Main()
{   
    "Lock".Dump();
    string temp;
    var a = new A();
    var watch = Stopwatch.StartNew();
    for (int i = 0; i < 100000000; ++i)
    {
        temp = a.Property;
        a.Property = temp;
    }
    Console.WriteLine("Normaly: " + watch.ElapsedMilliseconds + " ms");
    Test(a);

    "Double Barrier".Dump();
    var b = new B();
    watch.Restart();
    for (int i = 0; i < 100000000; ++i)
    {
        temp = b.Property;
        b.Property = temp;
    }
    Console.WriteLine("Normaly: " + watch.ElapsedMilliseconds + " ms");
    Test(b);

    "volatile".Dump();
    var c = new C();
    watch.Restart();
    for (int i = 0; i < 100000000; ++i)
    {
        temp = c.Property;
        c.Property = temp;
    }
    Console.WriteLine("Normaly: " + watch.ElapsedMilliseconds + " ms");
    Test(c);

    "Volatile Read/Write".Dump();
    var d = new D();
    watch.Restart();
    for (int i = 0; i < 100000000; ++i)
    {
        temp = d.Property;
        d.Property = temp;
    }
    Console.WriteLine("Normaly: " + watch.ElapsedMilliseconds + " ms");
    Test(d);

    "ReaderWriterLockSlim".Dump();
    var e = new E();
    watch.Restart();
    for (int i = 0; i < 100000000; ++i)
    {
        temp = e.Property;
        e.Property = temp;
    }
    Console.WriteLine("Normaly: " + watch.ElapsedMilliseconds + " ms");
    Test(e);

    "Single Barrier: freshness of Property".Dump();
    var f = new F();
    watch.Restart();
    for (int i = 0; i < 100000000; ++i)
    {
        temp = f.Property;
        f.Property = temp;
    }
    Console.WriteLine("Normaly: " + watch.ElapsedMilliseconds + " ms");
    Test(f);

    "Single Barrier: other not reodering".Dump();
    var g = new G();
    watch.Restart();
    for (int i = 0; i < 100000000; ++i)
    {
        temp = g.Property;
        g.Property = temp;
    }
    Console.WriteLine("Normaly: " + watch.ElapsedMilliseconds + " ms");
    Test(g);
}

void Test(I a)
{
    string temp;
    var watch = Stopwatch.StartNew();
    for (int i = 0; i < 100000000; ++i)
    {
        temp = a.Property;
        a.Property = temp;
    }

    Console.WriteLine("Passed as interface: " + watch.ElapsedMilliseconds + " ms\n");
}

interface I
{
    string Property { get; set; }
}

class A : I
{
    private string field;
    private readonly object syncLock = new object();

    public string Property
    {
        get
        {
            lock (syncLock)
            {
                return field;
            }
        }
        set
        {
            lock (syncLock)
            {
                field = value;
            }
        }
    }
}

class B : I
{
    private string field;

    public string Property
    {
        get
        {
            Thread.MemoryBarrier();
            string result = field;
            Thread.MemoryBarrier();
            return result;
        }
        set
        {
            Thread.MemoryBarrier();
            field = value;
            Thread.MemoryBarrier();
        }
    }
}

class C : I
{
    private volatile string field;

    public string Property
    {
        get
        {
            return field;
        }
        set
        {
            field = value;
        }
    }
}

class D : I
{
    private string field;

    public string Property
    {
        get
        {
            return Volatile.Read(ref field);
        }
        set
        {
            Volatile.Write(ref field, value);
        }
    }
}

class E : I
{
    private string field;
    private ReaderWriterLockSlim locker = new ReaderWriterLockSlim();

    public string Property
    {
        get
        {
            locker.EnterReadLock();
            string result = field;
            locker.ExitReadLock();
            return result;
        }
        set
        {
            locker.EnterReadLock();
            field = value;
            locker.ExitReadLock();
        }
    }
}

class F : I
{
    private string field;

    public string Property
    {
        get
        {
            Thread.MemoryBarrier();
            return field;
        }
        set
        {
            field = value;
            Thread.MemoryBarrier();
        }
    }
}

class G : I
{
    private string field;

    public string Property
    {
        get
        {
            string result = field;
            Thread.MemoryBarrier();
            return result;
        }
        set
        {
            Thread.MemoryBarrier();
            field = value;
        }
    }
}

3 个答案:

答案 0 :(得分:9)

  

线程安全性有什么不同吗?

两者都确保在读写时设置适当的障碍。

  

结果

在这两种情况下,两个线程都可以竞争写入值。但是,读取和写入不能在锁定或完全围栏之后向前或向后移动。

  

性能

你已经两种方式编写了代码。 现在运行。如果您想知道哪个更快,请运行并找出答案!如果你有两匹马并且你想知道哪匹马更快,那么就比赛吧。不要在网上问陌生人他们认为哪匹马更快。

也就是说,更好的技术是设置性能目标,编写代码以明确正确,然后测试以确定您是否达到了目标。如果你这样做了,不要浪费你宝贵的时间来尝试优化已经足够快的代码;用它来优化其他不够快的东西。

你没问过的问题:

  

你会做什么?

我不会写一个多线程程序,这就是我要做的。如果必须的话,我会使用进程作为我的并发单元。

如果我必须编写一个多线程程序,那么我将使用最高级别的工具。我使用任务并行库,我使用async-await,我使用Lazy<T>等等。我会避免共享记忆;我将线程视为以异步方式返回值的轻量级进程。

如果我必须编写共享内存多线程程序,那么我会始终锁定所有内容。这些天我们经常编写程序,通过卫星链接获取十亿字节的视频并将其发送到手机。花在锁上20纳秒并不会杀了你。

我不够聪明,试图编写低锁代码,所以我根本不会这样做。如果我不得不我将使用低锁代码来构建更高级别的抽象并使用该抽象。幸运的是,我没有必要,因为有人已经建立了我需要的抽象。

答案 1 :(得分:1)

只要有问题的变量是可以原子获取/设置的有限变量集(即引用类型)之一,那么是的,这两个解决方案正在应用相同的线程相关约束。

那就是说,我会诚实地期望MemoryBarrier解决方案执行更糟而不是锁定。访问无争议的lock非常快。它已针对该情况进行了专门优化。另一方面,引入内存屏障,不仅影响对一个变量的访问,如lock的情况,而所有内存 ,可能很容易在应用程序的其他方面产生重大的负面性能影响。您当然需要进行一些测试以确保(对于您的实际应用程序,因为单独测试这两个应用程序并不能揭示内存屏障正在强制应用程序的所有其他内容的事实) ; s要同步的内存,而不仅仅是这一个变量)。

答案 2 :(得分:0)

就线程安全而言,没有区别。但是,我更愿意:

private SomeType field

public SomeType Property
{
    get
    {
        return Volatile.Read(ref field);
    }
    set
    {
        Volatile.Write(ref field, value);
    }
}

或者,

private volatile SomeType field

public SomeType Property
{
    get
    {
        return field;
    }
    set
    {
        field = value;
    }
}