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

Pel*_*red 4 .net c# 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

和lock声明(Monitor.Enter和Monitor.Exit)

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

public SomeType Property
{
    get
    {
        lock (syncLock)
        {
            return field;
        }
    }
    set
    {
        lock (syncLock)
        {
            field = value;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

性能 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
Run Code Online (Sandbox Code Playgroud)

以下是我在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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 9

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

两者都确保围绕读写设置适当的障碍.

结果?

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

性能?

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

也就是说,一个更好的技术是设定一个性能目标,编写清楚正确的代码,然后测试你是否达到目标.如果你这样做了,不要浪费你宝贵的时间来尝试优化已经足够快的代码; 花钱去优化其他不够快的东西.

你没问的一个问题:

你会怎么做?

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

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

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

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