Program Club

SyncRoot 패턴의 용도는 무엇입니까?

proclub 2020. 11. 20. 16:56
반응형

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? '인스턴스의 대상'?

누구든지 위의 두 가지 접근 방식의 차이점을 말할 수 있습니까?


여러 스레드에 의한 동시 액세스를 방지하려는 내부 데이터 구조가있는 경우 항상 잠그고있는 개체가 공용이 아닌지 확인해야합니다.

그이면의 이유는 공개 객체는 누구나 잠글 수 있으므로 잠금 패턴을 완전히 제어 할 수 없기 때문에 교착 상태를 만들 수 있기 때문입니다.

이는 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민간 분야, 당신은이 객체의 외부 사용에 대해 걱정할 필요가 없습니다.


이 패턴의 실제 목적은 래퍼 계층과 올바른 동기화를 구현하는 것입니다.

예를 들어 WrapperA 클래스가 ClassThanNeedsToBeSynced의 인스턴스를 래핑하고 WrapperB 클래스가 ClassThanNeedsToBeSynced의 동일한 인스턴스를 래핑하는 경우 WrapperA 또는 WrapperB를 잠글 수 없습니다 .WrapperA를 잠그면 WrappedB에 대한 잠금이 기다리지 않기 때문입니다. 이러한 이유로 ClassThanNeedsToBeSynced의 잠금을 위임하는 wrapperAInst.SyncRoot 및 wrapperBInst.SyncRoot를 잠 가야합니다.

예:

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

이 주제와 관련된 또 다른 흥미로운 점이 있습니다.

컬렉션에 대한 SyncRoot의 의심스러운 가치 (Brad Adams) :

.NET의 SyncRoot많은 컬렉션에서 속성을 확인할 수 System.Collections있습니다. 회고전 (원문)에서는이 속성이 실수라고 생각합니다. 우리 팀의 프로그램 관리자 인 Krzysztof Cwalina가 그 이유에 대해 몇 가지 생각을 보냈습니다. 그에 동의합니다.

SyncRoot대부분의 시나리오 에서 기반 동기화 API가 불충분하게 유연하다는 사실을 발견했습니다 . API를 사용하면 컬렉션의 단일 구성원에 대한 스레드 안전 액세스가 가능합니다. 문제는 여러 작업을 잠 가야하는 여러 시나리오가 있다는 것입니다 (예 : 한 항목을 제거하고 다른 항목을 추가). 즉, 일반적으로 컬렉션 자체가 아니라 올바른 동기화 정책을 선택하려고 (실제로 구현할 수있는) 컬렉션을 사용하는 코드입니다. 우리 SyncRoot는 실제로 매우 드물게 사용되며 사용되는 경우 실제로 많은 가치를 추가하지 않습니다. 사용되지 않는 경우 ICollection.

이러한 컬렉션의 일반 버전을 빌드 할 때 동일한 실수를하지 않을 것입니다.


를 참조하십시오 제프 리히터의 기사를. 보다 구체적으로 "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);
    }
}

이 예제에서 첫 번째 호출은 성공하지만 디버거에서 추적하면 잠금이 해제 될 때까지 DoSomething에 대한 호출이 차단됩니다. 두 번째 호출은 주 스레드가 test 에 대한 모니터 잠금을 보유하고 있기 때문에 교착 상태가됩니다 .

The issue is that Main can lock the object instance, which means that it can keep the instance from doing anything that the object thinks should be synchronized. The point being that the object itself knows what requires locking, and outside interference is just asking for trouble. That's why the pattern of having a private member variable that you can use exclusively for synchronization without having to worry about outside 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 to synchronize on, not the Type.

참고URL : https://stackoverflow.com/questions/728896/whats-the-use-of-the-syncroot-pattern

반응형