【问题标题】:What's the use of the SyncRoot pattern?SyncRoot 模式有什么用?
【发布时间】:2010-10-18 06:02:57
【问题描述】:

我正在阅读一本描述 SyncRoot 模式的 c# 书籍。它显示

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

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

并与 SyncRoot 模式进行比较:

object syncRoot = new object();

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

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

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

这本书描述了...因为实例的对象也可以用于从外部同步访问并且你不能从类本身控制这个形式,你可以使用 SyncRoot 模式诶? '实例的对象'?

谁能告诉我上面两种方法的区别?

【问题讨论】:

  • 在第二个例子中,syncRoot() 应该是 doThat() 吗?
  • "object of the instance" == "instance of the class" 措辞不佳的书。
  • 感谢 Will Dean 的编辑。该死的复制/粘贴:)

标签: c# multithreading design-patterns concurrency


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 好的。 SyncRoot 是从哪里来的? .NET FCL 设计者为什么要创建.SyncRoot?或者,套用 Ryan 的问题,“SyncRoot 模式有什么用?”
  • 如果这很糟糕,那么为什么 CLR 设计师首先要创建 lock 键盘?他们为什么不创建类 Lock,您可以将其实例放入 _syncRoot 字段中?这将从每个对象的标头中移除锁定结构开销
  • @IanBoyd - .SyncRoot 为开发人员提供了一个明确的选择,只要他们锁定集合(本身或它的 SyncRoot),以说明他们是否想要与实现集合的代码中的任何锁定进行交互,或者保证不与之交互。这主要影响包装其他集合(继承、装饰器、复合......)的代码实现集合,而不是说这是好是坏。说大多数收藏的用户不需要关心。
【解决方案2】:

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

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

例子:

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

【讨论】:

  • 感谢您实际回答问题!
【解决方案3】:

这是一个例子:

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

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

正确的 SyncRoot 实现是:

object syncRoot = new object();

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

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

由于syncRoot是一个私有字段,你不必担心这个对象的外部使用。

【讨论】:

    【解决方案4】:

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

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

    您会在System.Collections 中的许多集合上注意到SyncRoot 属性。回想起来(原文如此),我认为这个属性是一个错误。我团队的项目经理 Krzysztof Cwalina 刚刚向我发送了一些关于为什么会这样的想法 - 我同意他的观点:

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

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

    【讨论】:

    • 集合上的 SyncRoot 与本主题所讨论的不同,将私有字段 SyncRoot 公开与锁定“this”一样糟糕。本主题是将私有只读字段上的锁定与“this”上的锁定进行比较,在集合的情况下,私有字段可通过公共属性访问,这不是最佳实践,并且可能导致死锁。
    • @Haze 你能解释一下“通过公共属性可以访问的部分,这不是最佳实践并且可能导致死锁”。这里怎么会发生死锁?
    • @prabhakaran 锁定this、公共字段或通过公共属性公开的私有字段的问题是任何人都可以访问它,如果您不知道这可能会导致死锁实施。类的设计方式不应要求正确使用类的实现知识。
    • @prabhakaran 具体例子请看 Darren Clark 的回答。
    【解决方案5】:

    参见thisJeff Richter 的文章。更具体地说,这个例子展示了锁定“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...
          }
       }
    }
    

    【讨论】:

    • 仅链接答案。示例表明可以构造恶意代码以致死锁。链接是悬空的。
    【解决方案6】:

    另一个具体的例子:

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

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

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-10
      • 2012-05-18
      相关资源
      最近更新 更多