SyncRoot模式的用途是什么?

Rya*_*yan 64 c# concurrency multithreading design-patterns

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

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

void doThat()
{
    lock(this){ ... }
}
Run Code Online (Sandbox Code Playgroud)

并与SyncRoot模式进行比较:

object syncRoot = new object();

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

void doThat()
{
    lock(syncRoot){ ... }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

ang*_*son 74

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

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

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

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

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

  • 好.`SyncRoot`在哪里进来?为什么.NET FCL设计者创建`.SyncRoot`?或者,用Ryan的问题来解释,*"SyncRoot模式有什么用?"* (15认同)
  • 我真的很困惑 SyncRoot 是公开的吗?请参阅 MSDN:https://learn.microsoft.com/en-us/dotnet/api/system.array.syncroot?view=net-7.0 (2认同)

ybo*_*ybo 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

正确的SyncRoot实现是:

object syncRoot = new object();

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

void doThat()
{
    lock(syncRoot ){ ... }
}
Run Code Online (Sandbox Code Playgroud)

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


Igo*_*ejc 13

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

ColleRoot在集合上的可疑值(由Brad Adams提供):

您会注意到System.Collections中许多集合上的SyncRoot属性.在回顾中,我认为这个属性是错误的.Krzysztof Cwalina,我的团队的项目经理,刚刚给我发了一些关于为什么这样做的想法 - 我同意他的看法:

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

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

  • 集合上的SyncRoot与本主题所讨论的不同,使私有字段SyncRoot公开与锁定"this"一样糟糕.主题是将私有只读字段上的锁定与锁定"this"进行比较,如果是集合,则私有字段可通过公共属性访问,这不是最佳实践,可能导致死锁. (3认同)

Rom*_*lov 13

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

例如,如果类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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢你真的回答了这个问题! (2认同)

Ant*_*lev 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...
      }
   }
}
Run Code Online (Sandbox Code Playgroud)