SyncRoot模式有什么用?
我正在阅读描述SyncRoot模式的ac#书。 表明
void doThis() { lock(this){ ... } } void doThat() { lock(this){ ... } }
并与SyncRoot模式进行比较:
object syncRoot = new object(); void doThis() { lock(syncRoot ){ ... } } void doThat() { lock(syncRoot){ ... } }
不过,我不太了解这里的区别。 似乎在两种情况下,两种方法一次只能由一个线程访问。
本书描述了…因为实例的对象也可以用于从外部进行同步访问,并且不能控制这个表格的类本身,所以可以使用SyncRoot模式 Eh? “实例的对象”?
谁能告诉我上面两种方法的区别?
提前致谢
如果你有一个内部的数据结构,你想阻止multithreading同时访问,你应该始终确保你locking的对象不公开。
这背后的原因是一个公共对象可以被任何人locking,因此你可以创build死锁,因为你不完全控制locking模式。
这意味着lockingthis
不是一个选项,因为任何人都可以locking该对象。 同样,你不应该locking你暴露给外部世界的东西。
这意味着最好的解决scheme是使用一个内部对象,因此小技巧就是使用Object
。
locking数据结构是您真正需要完全控制的东西,否则您可能会设置死锁的场景,这可能会遇到问题。
这里是一个例子:
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(); } } }
您会看到第二个类可以在locking语句中使用第一个类的实例。 这导致了这个例子中的僵局。
正确的SyncRoot实现是:
object syncRoot = new object(); void doThis() { lock(syncRoot ){ ... } } void doThat() { lock(syncRoot ){ ... } }
由于syncRoot
是一个私有字段,因此您不必担心外部使用该对象。
这里还有一个与这个主题有关的有趣的事情:
SyncRoot集合上的可疑值(由Brad Adams撰写) :
您会注意到System.Collections中许多集合上的SyncRoot属性。 回想起来,我认为这个属性是一个错误。 我团队的程序pipe理员Krzysztof Cwalina刚刚给了我一些想法,为什么这么说 – 我同意他的看法:
我们发现基于SyncRoot的同步API在大多数情况下都不够灵活。 API允许线程安全地访问集合的单个成员。 问题是有很多情况下,你需要locking多个操作(例如删除一个项目,并添加另一个)。 换句话说,它通常是使用一个集合的代码,它希望select(并且实际上可以实现)正确的同步策略,而不是集合本身。 我们发现SyncRoot实际上很less使用,在使用的时候,实际上并没有增加太多价值。 在不使用的情况下,对ICollection的实施者来说只是一个烦恼。
放心吧,我们不会因为构build这些集合的通用版本而犯同样的错误
这种模式的实际目的是实现与包装层次结构的正确同步。
例如,如果类WrapperA包装了ClassThanNeedsToBeSynced的一个实例,并且类WrapperB包装了ClassThanNeedsToBeSynced的同一个实例,则无法lockingWrapperA或WrapperB,因为如果lockingWrapperA,则WrappedB上的locking不会等待。 为此,您必须lockingwrapperAInst.SyncRoot和wrapperBInst.SyncRoot,它将locking委托给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 } } }
看这个 Jeff Richter的文章。 更具体地说,这个例子certificatelocking“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... } } }
另一个具体例子:
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); } }
在这个例子中,第一个调用会成功,但是如果你在debugging器中跟踪,DoSomething的调用将被阻塞,直到释放锁。 第二个调用会死锁,因为主线程正在testing监视器锁。
问题是Main可以locking对象实例,这意味着它可以让实例不做任何对象认为应该同步的事情。 关键是对象本身知道什么需要locking,而外部的干扰只是要求麻烦。 这就是为什么具有私有成员variables的模式,您可以专门用于同步,而不必担心外部干扰。
同样的静态模式也是如此:
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); } }
使用私有静态对象来同步,而不是types。