简体   繁体   English

锁定部分是否始终保证螺纹安全?

[英]Does lock section always guarantee thread safety?

I'm trying to understand thread-safe access to fields. 我正在尝试理解对字段的线程安全访问。 For this, i implemented some test sample: 为此,我实现了一些测试样本:

class Program
{   
    public static void Main()
    {
        Foo test = new Foo();
        bool temp;

        new Thread(() => { test.Loop = false; }).Start();

        do
        {
            temp = test.Loop;
        }
        while (temp == true);
    }
}

class Foo
{
    public bool Loop = true;
}

As expected, sometimes it doesn't terminate. 正如所料,有时它不会终止。 I know that this issue can be solved either with volatile keyword or with lock. 我知道可以使用volatile关键字或使用lock来解决此问题。 I consider that i'm not author of class Foo, so i can't make field volatile. 我认为我不是Foo类的作者,所以我不能让字段变得不稳定。 I tried using lock: 我试过用锁:

public static void Main()
{
    Foo test = new Foo();
    object locker = new Object();
    bool temp;

    new Thread(() => { test.Loop = false; }).Start();

    do
    {
        lock (locker)
        {
            temp = test.Loop;
        }
    }
    while (temp == true);
}

this seems to solve the issue. 这似乎解决了这个问题。 Just to be sure i moved the cycle inside the lock block: 只是为了确保我将循环移动到锁定块内:

lock(locker)
{
    do
    {
        temp = test.Loop;
    }
    while (temp == true);
}

and... the program does not terminates anymore. 并且...程序不再终止。

It is totally confusing me. 这让我很困惑。 Doesn't lock provides thread-safe access? 不锁定提供线程安全访问? If not, how to access non-volatile fields safely? 如果没有,如何安全地访问非易失性字段? I could use VolatileRead(), but it is not suitable for any case, like not primitive type or properties. 我可以使用VolatileRead(),但它不适用于任何情况,例如非原始类型或属性。 I considered that Monitor.Enter does the job , Am i right? 我认为Monitor.Enter完成了这项工作 ,我是对的吗? I don't understand how could it work. 我不明白它是如何工作的。

This piece of code: 这段代码:

do
{
    lock (locker)
    {
        temp = test.Loop;
    }
}
while (temp == true);

works because of a side-effect of lock : it causes a 'memory-fence' . 因为lock的副作用而起作用:它会导致“记忆栅栏” The actual locking is irrelevant here. 实际锁定在这里无关紧要。 Equivalent code: 等效代码:

do
{
   Thread.MemoryBarrier();   
   temp = test.Loop;       
}
while (temp == true);

And the issue you're trying to solve here is not exactly thread-safety, it is about caching of the variable (stale data). 你在这里尝试解决的问题并不完全是线程安全的,而是关于缓存变量(陈旧数据)。

It does not terminate anymore because you are accessing the variable outside of the lock as well. 它不再终止,因为您也在锁外访问变量。

In

new Thread(() => { test.Loop = false; }).Start();

you write to the variable outside the lock. 你写入锁外的变量。 This write is not guaranteed to be visible. 此写入不保证可见。

Two concurrent accesses to the same location of which at least one is a write is a data race. 对同一位置的两次并发访问(其中至少一个是写入)是数据争用。 Don't do that. 不要那样做。

Lock provides thread safety for 2 or more code blocks on different threads, that uses the lock. Lock为使用锁的不同线程上的2个或更多代码块提供线程安全性。 Your Loop assignment inside the new thread declaration is not enclosed in lock. 新线程声明中的循环赋值未包含在锁定中。 That means there is no thread safety there. 这意味着那里没有线程安全。

In general, no, lock is not something that will magically make all code inside it thread-safe. 一般来说,不, lock不是神奇地使其中的所有代码都是线程安全的东西。

The simple rule is: If you have some data that's shared by multiple threads, but you always access it only inside a lock (using the same lock object), then that access is thread-safe. 简单的规则是:如果您有一些由多个线程共享的数据,但您始终只在锁内(使用相同的锁对象)访问它,那么该访问是线程安全的。

Once you leave that “simple” code and start asking questions like “How could I use volatile / VolatileRed() safely here?” or “Why does this code that doesn't use lock properly seem to work?”, things get complicated quickly. 一旦你离开那个“简单”的代码并开始提问“我怎么能在这里安全地使用volatile / VolatileRed() ?”或“为什么这个没有正确使用锁的代码似乎工作?”,事情很快变得复杂。 And you should probably avoid that, unless you're prepared to spend a lot of time learning about the C# memory model. 除非你准备花很多时间学习C#内存模型,否则你应该避免这种情况。 And even then, bugs that manifest only once in million runs or only on certain CPUs (ARM) are very easy to make. 即便如此,只有一百万次运行或仅在某些CPU(ARM)上出现的错误很容易实现。

Locking only works when all access to the field is controlled by a lock. 锁定仅在通过锁控制对场的所有访问时起作用。 In your example only the reading is locked, but since the writing is not, there is no thread-safety. 在您的示例中,只有读取被锁定,但由于写入不是,因此没有线程安全性。

However it is also crucial that the locking takes place on a shared object, otherwise there is no way for another thread to know that someone is trying to access the field. 但是,锁定发生在共享对象上也是至关重要的,否则另一个线程无法知道有人正在尝试访问该字段。 So in your case when locking on an object which is only scoped inside the Main method, any other call on another thread, would not be able to block. 因此,在您锁定一个仅限在Main方法范围内的对象的情况下,另一个线程上的任何其他调用都无法阻止。

If you have no way to change Foo, the only way to obtain thread-safety is to have ALL calls actually lock on the same Foo instance. 如果你无法改变Foo,获得线程安全的唯一方法是让所有调用实际锁定在同一个Foo实例上。 This would generally not be recommended though, since all methods on the object would be locked. 但通常不建议这样做,因为对象上的所有方法都将被锁定。

The volatile keyword is not a guarantuee of thread-safety in itself. volatile关键字本身并不是线程安全的保证。 It is meant to indicate that the value of a field can be changed from different threads, and so any thread reading that field, should not cache it, since the value could change. 它意味着可以指示字段的值可以从不同的线程更改,因此读取该字段的任何线程都不应该缓存它,因为值可能会更改。

To achieve thread-safety, Foo should probably look something along these lines: 为了实现线程安全,Foo应该看起来像这样:

class Program
{   
    public static void Main()
    {
        Foo test = new Foo();
        test.Run();

        new Thread(() => { test.Loop = false; }).Start();

        do
        {            
            temp = test.Loop;
        }
        while (temp == true);
    }
}

class Foo
{
    private volatile bool _loop = true;
    private object _syncRoot = new object();

    public bool Loop
    {
        // All access to the Loop value, is controlled by a lock on an instance-scoped object. I.e. when one thread accesses the value, all other threads are blocked.
        get { lock(_syncRoot) return _loop; }
        set { lock(_syncRoot) _loop = value; }
    }

    public void Run()
    {
        Task(() => 
        {
            while(_loop) // _loop is volatile, so value is not cached
            {
                // Do something
            }
        });
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM