What is the use of the SyncRoot template?

I am reading an aC # book that describes the SyncRoot pattern. It shows

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

and compares with the SyncRoot pattern:

 object syncRoot = new object(); void doThis() { lock(syncRoot ){ ... } } void doThat() { lock(syncRoot){ ... } } 

However, I do not really understand the difference here; it seems that in both cases, both methods can only be accessed by one thread at a time.

The book describes ... because the instance object can also be used for synchronized access from outside, and you cannot control this form of the class itself, can you use the SyncRoot Eh template? "instance object"?

Can someone tell me the difference between the two approaches above?

+60
multithreading c # design-patterns concurrency
Apr 08 '09 at 7:37
source share
6 answers

If you have an internal data structure that you want to prevent multiple threads from accessing simultaneously, you should always make sure that the object you are blocking is not public.

The reason for this is that a public object can be blocked by anyone, and thus you can create deadlocks because you do not completely control the blocking pattern.

This means that locking on this not an option, since anyone can lock this object. Similarly, you should not block what you open to the outside world.

This means that the best solution is to use an internal object, and so the tip should just use Object .

Blocking data structures is what you really need to have complete control, otherwise you run the risk of creating a deadlock scenario, which can be very problematic to process.

+71
Apr 08 '09 at 7:43
source share

Here is an example:

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

You see that the second class can use an instance of the first in the lock statement. This leads to a deadlock in this example.

Correct implementation of SyncRoot:

 object syncRoot = new object(); void doThis() { lock(syncRoot ){ ... } } void doThat() { lock(syncRoot ){ ... } } 

as syncRoot is a private field, you do not need to worry about the external use of this object.

+18
Apr 08 '09 at 7:45
source share

Here is another interesting thing related to this topic:

The dubious value of SyncRoot on collections (Brad Adams) :

You will notice the SyncRoot property in many collections in System.Collections. In retrospect (sic), I think this property was a mistake. Krzysztof Kvalina, the program manager on my team, just shared some thoughts with me about why this is so - I agree with him:

We found that SyncRoot-based synchronization APIs are not flexible enough for most scenarios. The APIs provide thread-safe access to a single member of the collection. The problem is that there are many scenarios in which you need to block several operations (for example, delete one element and add another). In other words, this is usually code that uses a collection that wants to select (and can actually implement) the correct synchronization policy, and not the collection itself. We found that SyncRoot is actually used very rarely, and when it is used, it does not really add much value. In those cases when it is not used, it just annoys the developers of ICollection.

Rest assured, we will not make the same mistake as when creating universal versions of these collections.

+13
Apr 08 '09 at 7:50
source share

The actual purpose of this template is to properly synchronize with the wrapper hierarchy.

For example, if the WrapperA class wraps an instance of ClassThanNeedsToBeSynced, and the WrapperB class wraps the same instance of ClassThanNeedsToBeSynced, you cannot block WrapperA or WrapperB, since if you lock WrapperA, the WrappedB lock will not wait. For this reason, you must lock wrapperAInst.SyncRoot and wrapperBInst.SyncRoot, which delegate the ClassThanNeedsToBeSynced lock.

Example:

 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 } } } 
+11
Dec 15 '11 at 12:38
source share

See this article by Jeff Richter. More specifically, this example demonstrates that locking to "this" can cause a dead end:

 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 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 Finalize method ~App() { // For demonstration purposes, have the CLR // 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... } } } 
+6
Apr 08 '09 at 7:46
source share

Another specific example:

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

In this example, the first call will succeed, but if you track in the debugger, the DoSomething call will block until the lock is released. The second call will be blocked, since the main thread holds the monitor lock on the test .

The problem is that Main can block the instance of the object, which means that it can force the instance to do something that the object considers synchronized. The fact is that the object itself knows that it requires blocking, and external intervention just requires trouble. This is why the pattern of having a private member variable that you can use exclusively for synchronization without worrying about external interference.

The same goes for the equivalent static pattern:

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

Use a private static object for synchronization, not a type.

+2
Apr 08 '09 at 8:02
source share



All Articles