线程.MemoryBarrier和lock的一个简单属性的区别

本文关键字:一个 简单 区别 属性 MemoryBarrier lock 线程 | 更新日期: 2023-09-27 17:50:34

对于以下场景,在线程安全性、结果和性能方面与使用MemoryBarrier

有什么不同吗?
private SomeType field;
public SomeType Property
{
    get
    {
        Thread.MemoryBarrier();
        SomeType result = field;
        Thread.MemoryBarrier();
        return result;
    }
    set
    {
        Thread.MemoryBarrier();
        field = value;
        Thread.MemoryBarrier();
    }
}

and lock语句(Monitor.Enter and Monitor.Exit)

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

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

对于Release, memorybarrier的实现速度大约是lock的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;
        }
    }
}

线程.MemoryBarrier和lock的一个简单属性的区别

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

两者都确保在读和写操作周围设置适当的屏障。

结果吗?

在这两种情况下,两个线程都可以竞争写一个值。但是,读和写不能向前或向后移动超过锁或满栅栏。

性能?

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

也就是说,更好的技术是设置性能目标,编写明确正确的代码,然后进行测试,看看是否达到了目标。如果是这样,就不要浪费宝贵的时间去优化已经足够快的代码;把时间花在优化其他速度不够快的东西上。

一个你没有问的问题:

你会怎么做?

我不会写多线程程序,那是我要做的。如果有必要,我会使用进程作为并发单位。

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

如果我必须写一个共享内存多线程程序,那么我会锁定所有内容,所有时间。如今,我们经常编写程序,通过卫星链接获取十亿字节的视频,并将其发送到手机上。花20纳秒的时间撬开一把锁不会要了你的命。

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

只要所讨论的变量是可以自动获取/设置的有限变量集之一(即引用类型),那么是的,这两种解决方案应用了相同的线程相关约束。

也就是说,我真诚地期望MemoryBarrier解决方案比锁执行更差。访问无竞争的lock非常快。它专门针对这种情况进行了优化。另一方面,引入一个内存屏障,它不仅影响对这个变量的访问(如lock的情况),而且影响所有内存,这很容易对应用程序的其他方面产生显著的负面性能影响。当然,您需要做一些测试来确保(在您的实际应用程序中,因为单独测试这两个变量并不能揭示内存屏障迫使应用程序内存的所有其余部分同步的事实,而不仅仅是这一个变量)。

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

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;
    }
}