SyncRoot模式的用途是什么?


68

我正在阅读一本描述SyncRoot模式的书。表明

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

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

并与SyncRoot模式进行比较:

object syncRoot = new object();

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

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

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

这本书描述了……因为实例的对象也可以用于外部的同步访问,并且您无法控制类本身的形式,因此可以使用SyncRoot模式。“实例的对象”?

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


在第二个示例中,syncRoot()应该是doThat()吗?
Will Dean

9
“实例的对象” ==“类的实例”措辞不好的书。
达伦·克拉克

感谢您的编辑威尔·迪恩。该死的复制/粘贴:)
Ryan

Answers:


75

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

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

这意味着锁定this不是一个选择,因为任何人都可以锁定该对象。同样,您不应锁定暴露于外界的事物。

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

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


16
好。哪里SyncRoot进来的?.NET FCL设计器为何创建.SyncRoot?或者,用Ryan的问题来解释:“ SyncRoot模式的用途是什么?”
伊恩·博伊德

如果那很不好,那为什么CLR设计师为什么要首先创建锁定密钥库?他们为什么不创建Lock类,可以将其实例放入_syncRoot字段中?这将消除每个对象标头的开销锁定结构
Bogdan Mart

@IanBoyd-.SyncRoot为开发人员提供了一个明确的选择,无论他们何时锁定集合(本身或它的SyncRoot),以表明他们是要与实现集合的代码内的任何锁定进行交互,还是保证不与之交互那。这主要影响实现其他包装(继承,装饰器,复合材料...)的代码的实现,而不是说这是好是坏。说大多数馆藏用户不需要关心。
Jirka Hanika '16

18

这是一个例子:

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一个私有字段一样,您不必担心该对象的外部使用。


15

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

例如,如果类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
        }
    }
}

2
感谢您实际回答问题!
Govert,2015年

14

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

集合上SyncRoot的可疑值(作者Brad Adams)

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

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

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


3
集合上的SyncRoot与本主题讨论的主题不同,将私有字段SyncRoot公开为与锁定“ this”一样糟糕。主题是将对私有只读字段的锁定与对“ this”的锁定进行比较,如果是Collections,则可以通过公共属性访问私有字段,这不是最佳实践,并且可能导致死锁。
haze4real 2012年

@阴霾你能解释一下“通过公共财产可以访问这部分,这不是最佳实践,并且可能导致死锁”。这里怎么会发生僵局?
prabhakaran 2014年

@prabhakaran锁定this,公共字段或通过公共属性公开的私有字段的问题是,任何人都可以访问它,如果您不了解实现,可能会导致死锁。类的设计不应以正确使用类所需的知识来实现​​。
haze4real 2014年

@prabhakaran有关具体示例,请查看Darren Clark的答案。
haze4real 2014年

6

请参见杰夫·里希特的文章。更具体地说,此示例演示了锁定“ 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...
      }
   }
}

仅链接的答案。该示例显示可以构造恶意代码以使其陷入僵局。和链接悬而未决。
Jirka Hanika '16

2

另一个具体示例:

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.