SyncRoot模式的用途是什么?

时间:2009-04-08 07:37:53

标签: c# multithreading design-patterns concurrency

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

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

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

并与SyncRoot模式进行比较:

object syncRoot = new object();

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

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

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

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

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

6 个答案:

答案 0 :(得分:73)

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

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

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

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

锁定数据结构是你真正需要完全控制的东西,否则你可能会设置死锁的方案,这可能是非常有问题的。

答案 1 :(得分: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是私有字段,因此您无需担心此对象的外部使用。

答案 2 :(得分:13)

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

Questionable value of SyncRoot on Collections (by Brad Adams)

  

您会注意到SyncRoot中许多收藏集的System.Collections属性。在retrospeced(原文如此)中,我认为这个属性是错误的。 Krzysztof Cwalina,我的团队的项目管理员,刚刚给我发了一些关于为什么这样做的想法 - 我同意他的看法:

     

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

     

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

答案 3 :(得分:12)

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

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

答案 4 :(得分:6)

请参阅this Jeff 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...
      }
   }
}

答案 5 :(得分: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的调用将会阻塞,直到锁定被释放。第二个调用将死锁,因为主线程正在测试上持有显示器锁定。

问题是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。