C# SyncRoot 模式有什么用?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/728896/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 22:49:32  来源:igfitidea点击:

What's the use of the SyncRoot pattern?

c#multithreadingdesign-patternsconcurrency

提问by Ryan

I'm reading a c# book that describes the SyncRoot pattern. It shows

我正在阅读描述 SyncRoot 模式的 ac# book。表明

void doThis()
{
    lock(this){ ... }
}

void doThat()
{
    lock(this){ ... }
}

and compares to the SyncRoot pattern:

并与 SyncRoot 模式进行比较:

object syncRoot = new object();

void doThis()
{
    lock(syncRoot ){ ... }
}

void doThat()
{
    lock(syncRoot){ ... }
}

However, I don't really understand the difference here; it seems that in both cases both methods can only be accessed by one thread at a time.

但是,我不太明白这里的区别。似乎在这两种情况下,这两种方法一次只能由一个线程访问。

The book describes ... because the object of the instance can also be used for synchronized access from the outside and you can't control this form the class itself, you can use the SyncRoot patternEh? 'object of the instance'?

书中描述了...因为实例的对象也可以用于从外部同步访问而您无法控制这种形式的类本身,您可以使用SyncRoot模式嗯?“实例对象”?

Can anyone tell me the difference between the two approaches above?

谁能告诉我上述两种方法之间的区别?

采纳答案by Lasse V. Karlsen

If you have an internal data structure that you want to prevent simultaneous access to by multiple threads, you should always make sure the object you're locking on is not public.

如果您想要防止多个线程同时访问一个内部数据结构,您应该始终确保您锁定的对象不是公开的。

The reasoning behind this is that a public object can be locked by anyone, and thus you can create deadlocks because you're not in total control of the locking pattern.

这背后的原因是公共对象可以被任何人锁定,因此您可以创建死锁,因为您无法完全控制锁定模式。

This means that locking on thisis not an option, since anyone can lock on that object. Likewise, you should not lock on something you expose to the outside world.

这意味着锁定this不是一种选择,因为任何人都可以锁定该对象。同样,你不应该锁定你暴露给外界的东西。

Which means that the best solution is to use an internal object, and thus the tip is to just use Object.

这意味着最好的解决方案是使用内部对象,因此建议只使用Object.

Locking data structures is something you really need to have full control over, otherwise you risk setting up a scenario for deadlocking, which can be very problematic to handle.

锁定数据结构是您真正需要完全控制的事情,否则您可能会设置死锁场景,这可能会非常难以处理。

回答by ybo

Here is an example :

这是一个例子:

class ILockMySelf
{
    public void doThat()
    {
        lock (this)
        {
            // Don't actually need anything here.
            // In this example this will never be reached.
        }
    }
}

class WeveGotAProblem
{
    ILockMySelf anObjectIShouldntUseToLock = new ILockMySelf();

    public void doThis()
    {
        lock (anObjectIShouldntUseToLock)
        {
            // doThat will wait for the lock to be released to finish the thread
            var thread = new Thread(x => anObjectIShouldntUseToLock.doThat());
            thread.Start();

            // doThis will wait for the thread to finish to release the lock
            thread.Join();
        }
    }
}

You see that the second class can use an instance of the first one in a lock statement. This leads to a deadlock in the example.

您会看到第二个类可以在 lock 语句中使用第一个类的实例。这会导致示例中出现死锁。

The correct SyncRoot implementation is:

正确的 SyncRoot 实现是:

object syncRoot = new object();

void doThis()
{
    lock(syncRoot ){ ... }
}

void doThat()
{
    lock(syncRoot ){ ... }
}

as syncRootis a private field, you don't have to worry about external use of this object.

作为syncRoot私有字段,您不必担心此对象的外部使用。

回答by Anton Gogolev

See thisJeff Richter's article. More specifically, this example which demonstrates that locking on "this" can cause a deadlock:

请参见杰夫·里希特的文章。更具体地说,这个例子演示了锁定“this”会导致死锁:

using System;
using System.Threading;

class App {
   static void Main() {
      // Construct an instance of the App object
      App a = new App();

      // This malicious code enters a lock on 
      // the object but never exits the lock
      Monitor.Enter(a);

      // For demonstration purposes, let's release the 
      // root to this object and force a garbage collection
      a = null;
      GC.Collect();

      // For demonstration purposes, wait until all Finalize
      // methods have completed their execution - deadlock!
      GC.WaitForPendingFinalizers();

      // We never get to the line of code below!
      Console.WriteLine("Leaving Main");
   }

   // This is the App type's Finalize method
   ~App() {
      // For demonstration purposes, have the CLR's 
      // Finalizer thread attempt to lock the object.
      // NOTE: Since the Main thread owns the lock, 
      // the Finalizer thread is deadlocked!
      lock (this) {
         // Pretend to do something in here...
      }
   }
}

回答by Igor Brejc

Here's one other interesting thing related to this topic:

这是与此主题相关的另一件有趣的事情:

Questionable value of SyncRoot on Collections (by Brad Adams):

SyncRoot 对集合的可疑价值(由 Brad Adams)

You'll notice a SyncRootproperty on many of the Collections in System.Collections. In retrospeced (sic), I think this property was a mistake. Krzysztof Cwalina, a Program Manger on my team, just sent me some thoughts on why that is – I agree with him:

We found the SyncRoot-based synchronization APIs to be insufficiently flexible for most scenarios. The APIs allow for thread safe access to a single member of a collection. The problem is that there are numerous scenarios where you need to lock on multiple operations (for example remove one item and add another). In other words, it's usually the code that uses a collection that wants to choose (and can actually implement) the right synchronization policy, not the collection itself. We found that SyncRootis actually used very rarely and in cases where it is used, it actually does not add much value. In cases where it's not used, it is just an annoyance to implementers of ICollection.

Rest assured we will not make the same mistake as we build the generic versions of these collections.

你会发现一个SyncRoot在许多收藏的财产System.Collections。回想起来(原文如此),我认为这个属性是一个错误。我团队的项目经理 Krzysztof Cwalina 刚刚给我发了一些关于为什么会这样的想法——我同意他的观点:

我们发现SyncRoot基于 -based 的同步 API 对于大多数场景来说不够灵活。API 允许对集合的单个成员进行线程安全访问。问题是有很多场景需要锁定多个操作(例如删除一项并添加另一项)。换句话说,通常是使用集合的代码想要选择(并且可以实际实现)正确的同步策略,而不是集合本身。我们发现SyncRoot实际上很少使用,并且在使用它的情况下,它实际上并没有增加太多价值。在不使用它的情况下,它只是ICollection.

请放心,我们不会在构建这些集合的通用版本时犯同样的错误。

回答by Darren Clark

Another concrete example:

另一个具体的例子:

class Program
{
    public class Test
    {
        public string DoThis()
        {
            lock (this)
            {
                return "got it!";
            }
        }
    }

    public delegate string Something();

    static void Main(string[] args)
    {
        var test = new Test();
        Something call = test.DoThis;
        //Holding lock from _outside_ the class
        IAsyncResult async;
        lock (test)
        {
            //Calling method on another thread.
            async = call.BeginInvoke(null, null);
        }
        async.AsyncWaitHandle.WaitOne();
        string result = call.EndInvoke(async);

        lock (test)
        {
            async = call.BeginInvoke(null, null);
            async.AsyncWaitHandle.WaitOne();
        }
        result = call.EndInvoke(async);
    }
}

In this example, the first call will succeed, but if you trace in the debugger the call to DoSomething will block until the lock is release. The second call will deadlock, since the Main thread is holding the monitor lock on test.

在此示例中,第一次调用将成功,但如果您在调试器中进行跟踪,则对 DoSomething 的调用将阻塞,直到释放锁。第二个调用将死锁,因为主线程在test上持有监视器锁。

The issue is that Main can lock the object instance, which means that it can keep the instance from doing anything that the object thinks should be synchronized. The point being that the object itself knows what requires locking, and outside interference is just asking for trouble. That's why the pattern of having a private member variable that you can use exclusivelyfor synchronization without having to worry about outside interference.

问题是 Main 可以锁定对象实例,这意味着它可以阻止实例执行对象认为应该同步的任何事情。关键是对象本身知道什么需要锁定,而外部干扰只是自找麻烦。这就是为什么拥有一个私有成员变量的模式,您可以专门用于同步而不必担心外部干扰。

The same goes for the equivalent static pattern:

等效的静态模式也是如此:

class Program
{
    public static class Test
    {
        public static string DoThis()
        {
            lock (typeof(Test))
            {
                return "got it!";
            }
        }
    }

    public delegate string Something();

    static void Main(string[] args)
    {
        Something call =Test.DoThis;
        //Holding lock from _outside_ the class
        IAsyncResult async;
        lock (typeof(Test))
        {
            //Calling method on another thread.
            async = call.BeginInvoke(null, null);
        }
        async.AsyncWaitHandle.WaitOne();
        string result = call.EndInvoke(async);

        lock (typeof(Test))
        {
            async = call.BeginInvoke(null, null);
            async.AsyncWaitHandle.WaitOne();
        }
        result = call.EndInvoke(async);
    }
}

Use a private static object to synchronize on, not the Type.

使用私有静态对象进行同步,而不是类型。

回答by Roman Zavalov

The actual purpose of this pattern is implementing correct synchronization with wrappers hierarchy.

此模式的实际目的是实现与包装器层次结构的正确同步。

For example, if class WrapperA wraps an instance of ClassThanNeedsToBeSynced, and class WrapperB wraps the same instance of ClassThanNeedsToBeSynced, you can't lock on WrapperA or WrapperB, since if you lock on WrapperA, lock on WrappedB won't wait. For this reason you must lock on wrapperAInst.SyncRoot and wrapperBInst.SyncRoot, which delegate lock to ClassThanNeedsToBeSynced's one.

例如,如果类 WrapperA 包装了 ClassThanNeedsToBeSynced 的实例,而类 WrapperB 包装了 ClassThanNeedsToBeSynced 的同一个实例,则无法锁定 WrapperA 或 WrapperB,因为如果锁定 WrapperA,则锁定 WrappedB 将不会等待。为此,您必须锁定 wrapperAInst.SyncRoot 和 wrapperBInst.SyncRoot,它们将锁定委托给 ClassThanNeedsToBeSynced 的一个。

Example:

例子:

public interface ISynchronized
{
    object SyncRoot { get; }
}

public class SynchronizationCriticalClass : ISynchronized
{
    public object SyncRoot
    {
        // you can return this, because this class wraps nothing.
        get { return this; }
    }
}

public class WrapperA : ISynchronized
{
    ISynchronized subClass;

    public WrapperA(ISynchronized subClass)
    {
        this.subClass = subClass;
    }

    public object SyncRoot
    {
        // you should return SyncRoot of underlying class.
        get { return subClass.SyncRoot; }
    }
}

public class WrapperB : ISynchronized
{
    ISynchronized subClass;

    public WrapperB(ISynchronized subClass)
    {
        this.subClass = subClass;
    }

    public object SyncRoot
    {
        // you should return SyncRoot of underlying class.
        get { return subClass.SyncRoot; }
    }
}

// Run
class MainClass
{
    delegate void DoSomethingAsyncDelegate(ISynchronized obj);

    public static void Main(string[] args)
    {
        SynchronizationCriticalClass rootClass = new SynchronizationCriticalClass();
        WrapperA wrapperA = new WrapperA(rootClass);
        WrapperB wrapperB = new WrapperB(rootClass);

        // Do some async work with them to test synchronization.

        //Works good.
        DoSomethingAsyncDelegate work = new DoSomethingAsyncDelegate(DoSomethingAsyncCorrectly);
        work.BeginInvoke(wrapperA, null, null);
        work.BeginInvoke(wrapperB, null, null);

        // Works wrong.
        work = new DoSomethingAsyncDelegate(DoSomethingAsyncIncorrectly);
        work.BeginInvoke(wrapperA, null, null);
        work.BeginInvoke(wrapperB, null, null);
    }

    static void DoSomethingAsyncCorrectly(ISynchronized obj)
    {
        lock (obj.SyncRoot)
        {
            // Do something with obj
        }
    }

    // This works wrong! obj is locked but not the underlaying object!
    static void DoSomethingAsyncIncorrectly(ISynchronized obj)
    {
        lock (obj)
        {
            // Do something with obj
        }
    }
}