SyncRoot 模式有什么用?

我正在读一本描述 SyncRoot 模式的 c # 书

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


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

并与 SyncRoot 模式进行比较:

object syncRoot = new object();


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


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

然而,我并不真正理解这里的区别; 似乎在这两种情况下,两个方法一次只能被一个线程访问。

这本书描述了 ... 因为实例的对象也可以用于从外部进行同步访问,而且您不能控制类本身的这种形式,所以您可以使用 SyncRoot 模式呃? “实例的对象”?

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

34825 次浏览

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

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

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

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

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

这里有一个例子:

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

参见 这个 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...
}
}
}

关于这个话题,还有一件有趣的事情:

SyncRoot 在集合上的可疑价值(by Brad Adams)

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

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

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

另一个具体的例子:

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 线程持有 测试上的监视器锁。

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

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

例如,如果 WrapperA 类包装了 ClassThanNeedsToBeSynced 的一个实例,而 WrapperB 类包装了 ClassThanNeedsToBeSynced 的同一个实例,那么就不能锁定 WrapperA 或 WrapperB,因为如果锁定 WrapperA,那么锁定 WrappedB 就不会等待。 出于这个原因,您必须锁定 wrapperAInst.SyncRoot 和 wrapperBInst.SyncRoot,它们将锁委托给 ClassThanNeedsToBeSynces 的那个。

例如:

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