c# 구문 - 왜 잠금 객체는 정적이어야합니까?



lock process (4)

멀티 스레딩에서 잠금을 위해 전용 정적 읽기 전용 객체를 사용하는 것은 매우 일반적입니다. 개인적으로는 캡슐화를 강화하여 잠금 개체의 진입 점을 줄여서 가장 필수적인 요소에 액세스 할 수 있다고 생각합니다.

하지만 왜 정적입니까?

private static readonly object Locker = new object();

결국 필드는 내 수업에서만 사용되며, 대신 다음과 같이 사용할 수도 있습니다.

private readonly object Locker = new object();

다른하실 말씀 있나요?

최신 정보:

예를 들어이 코드를 붙여 넣었습니다 (예제 만). 이것에 정적 또는 비 정적 락커를 사용할 수 있었고 둘 다 잘 작동합니다. 아래 답변을 고려해 볼 때 나는 이런 식으로 사물함을 정의해야만합니까? (미안 해요. 다음 주 인터뷰를하고 모든 세부 사항을 알아야합니다. :)

private readonly object Locker = new object();

그리고 여기 코드가 있습니다 :

    private int _priceA;
    private int _priceB;
    private EventWaitHandle[] _waithandle;
    private readonly IService _service;

//ctor
public ModuleAViewModel(IService service)
    {
        _service = service;
        _modelA = new ModelA();
        _waithandle = new ManualResetEvent[2];
        _waithandle[0] = new ManualResetEvent(false);
        _waithandle[1] = new ManualResetEvent(false);
        LoadDataByThread();
    }


 private void LoadDataByThread()
        {
            new Thread(() =>
                           {
                               new Thread(() =>
                               {
                                   lock (Locker)
                                   {
                                       _priceA = _service.GetPriceA();
                                   }
                                   _waithandle[0].Set();
                               }).Start();

                               new Thread(() =>
                               {
                                   lock (Locker)
                                   {
                                       _priceB = _service.GetPriceB();
                                   }
                                   _waithandle[1].Set();
                               }).Start();

                               WaitHandle.WaitAll(_waithandle);
                               PriceA = _priceA;
                               PriceB = _priceB;
                           }).Start();
        }

감사


Answers

실제로 정적 일 필요는 없습니다 . 실제로는 정적이어서는 안됩니다 .

변수는 잠금에 사용하는 메소드와 동일한 범위에 있어야합니다. 메소드가 정적 인 경우 변수는 정적이어야하며 메소드가 인스턴스 메소드 인 경우 변수는 인스턴스가 가변적이어야합니다.

정적 변수는 인스턴스 메소드를 잠그기 위해 사용될 때도 작동하지만 너무 많이 잠글 수 있습니다. 동일한 인스턴스의 메소드뿐만 아니라 모든 인스턴스의 모든 메소드를 잠글 것입니다.


잠금 장치의 범위와 수명은 잠금하려는 '것'에 따라 달라질 수 있습니다. 정적 잠금은 주로 정적 인 것들을 잠그는 데 사용됩니다.


"멀티 스레딩에서 잠금을 위해 개인 정적 읽기 전용 객체를 사용하는 것이 매우 일반적이지는 않습니다"- 오히려 적절하거나 선택된 입도 로 잠금을 사용하는 것이 일반적입니다. 때로는 static 입니다. 더 자주, IMO, 그것은 아닙니다 - 그러나 인스턴스 기반입니다.

static 잠금을 보는 주요 시간은 글로벌 캐시 또는 글로벌 데이터 / 싱글 톤의 지연된로드입니다. 그리고 후자에는 어쨌든 그것을하는 더 좋은 방법이 있습니다.

그래서 정말 달라집니다 : 당신의 시나리오에서 Locker 는 어떻게 사용됩니까? 그 자체로 정적 인 것을 보호하고 있습니까? 그렇다면 잠금 장치가 고정되어 있어야합니다. 그것이 인스턴스 기반의 무언가를 보호한다면 IMO는 인스턴스 기반이어야한다.


먼저, 언어 변호사처럼 생각하는 법을 배워야합니다.

C ++ 사양은 특정 컴파일러, 운영 체제 또는 CPU를 참조하지 않습니다. 실제 시스템의 일반화 된 추상 기계 를 참조합니다. 언어 변호사의 세계에서 프로그래머는 추상적 기계 용 코드를 작성해야합니다. 컴파일러의 역할은 콘크리트 시스템에서 그 코드를 실현하는 것입니다. 스펙을 엄격하게 코딩하면 현재 또는 향후 50 년 동안 호환되는 C ++ 컴파일러가있는 시스템에서 코드를 수정하지 않고 컴파일하고 실행할 수 있습니다.

C ++ 98 / C ++ 03 사양의 추상 기계는 근본적으로 단일 스레드입니다. 따라서 스펙과 관련하여 "완벽하게 이식 가능"한 멀티 쓰레드 C ++ 코드를 작성하는 것은 불가능합니다. 이 스펙은 메모리로드 및 스토어의 원 자성 또는로드 및 저장이 발생할 수있는 순서 에 대해 아무 것도 말하지 않으며 뮤텍스와 같은 것을 신경 쓰지 않습니다.

물론 pthread 나 Windows와 같은 특정 구체적인 시스템에 대해 실제로 멀티 스레드 코드를 작성할 수 있습니다. 그러나 C ++ 98 / C ++ 03에 대한 다중 스레드 코드를 작성하는 표준 방법은 없습니다.

C ++ 11의 추상 기계는 설계 상 멀티 스레드입니다. 또한 잘 정의 된 메모리 모델을 가지고 있습니다 . 즉, 컴파일러가 메모리에 액세스 할 때 수행 할 수도 있고 수행하지 않을 수도있는 작업을 말합니다.

두 개의 스레드에 의해 한 쌍의 전역 변수가 동시에 액세스되는 다음 예제를 고려하십시오.

           Global
           int x, y;

Thread 1            Thread 2
x = 17;             cout << y << " ";
y = 37;             cout << x << endl;

스레드 2 출력은 무엇입니까?

C ++ 98 / C ++ 03에서는 이것이 정의되지 않은 동작조차되지 않습니다. 표준은 "스레드"라고하는 것을 고려하지 않기 때문에 질문 자체는 의미 가 없습니다.

C ++ 11에서로드 및 저장은 일반적으로 원자적일 필요가 없으므로 결과는 정의되지 않은 동작입니다. 어느 정도 향상되지 않은 것처럼 보일 수도 있습니다 ... 그리고 그 자체로는 그렇지 않습니다.

하지만 C ++ 11에서는 다음과 같이 작성할 수 있습니다.

           Global
           atomic<int> x, y;

Thread 1                 Thread 2
x.store(17);             cout << y.load() << " ";
y.store(37);             cout << x.load() << endl;

이제 상황이 훨씬 더 재미 있습니다. 우선, 여기서의 행동이 정의됩니다 . 이제 스레드 2는 0 0 (스레드 1 이전에 실행되는 경우), 37 17 (스레드 1 이후에 실행되는 경우) 또는 0 17 (스레드 1이 x에 할당 된 후 y에 할당되기 전에 실행되는 경우)를 인쇄 할 수 있습니다.

C ++ 11의 원자 적로드 / 저장소에 대한 기본 모드는 순차적 일관성 을 적용하기 때문에 인쇄 할 수없는 것은 37 0 입니다. 이것은 단순히 모든로드 및 저장소가 각 스레드 내에서 작성한 순서대로 "있는 그대로"있어야한다는 것을 의미하지만 스레드간에 작업을 인터리브 할 수 있지만 시스템에서는 좋아합니다. 따라서 원자의 기본 동작은로드 및 저장에 원 자성순서 를 제공합니다.

이제 현대 CPU에서 순차적 일관성을 유지하는 데 많은 비용이 듭니다. 특히, 컴파일러는 모든 액세스간에 본격적인 메모리 장벽을 내고 있습니다. 그러나 알고리즘이 순서가 잘못된로드 및 저장을 허용 할 수 있다면 즉, 원 자성이 필요하지만 순서가 필요하지 않은 경우; 즉,이 프로그램의 출력으로 37 0 을 허용 할 수 있다면 다음과 같이 작성할 수 있습니다.

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_relaxed);   cout << y.load(memory_order_relaxed) << " ";
y.store(37,memory_order_relaxed);   cout << x.load(memory_order_relaxed) << endl;

CPU가 현대적 일수록 이전 예제보다 더 빠를 가능성이 높습니다.

마지막으로, 특정로드 및 저장을 순서대로 유지해야하는 경우 다음을 작성할 수 있습니다.

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_release);   cout << y.load(memory_order_acquire) << " ";
y.store(37,memory_order_release);   cout << x.load(memory_order_acquire) << endl;

이렇게하면 순서가 지정된로드 및 저장으로 되돌아갑니다. 따라서 37 0 은 더 이상 출력이 불가능하지만 최소한의 오버 헤드로 수행됩니다. (이 간단한 예제에서는 결과가 본격적인 일관성과 동일하지만 큰 프로그램에서는 그렇지 않습니다.)

물론보고 싶은 출력이 0 0 또는 37 17 이면 원래 코드 주위에 뮤텍스를 래핑 할 수 있습니다. 그러나 당신이 이것을 멀리 읽었다면, 당신은 이미 그것이 어떻게 작동하는지 알았을 것입니다. 그리고이 답변은 이미 의도 한 것보다 더 오래갑니다 :-).

그래서 결론. 뮤텍스는 훌륭하고 C ++ 11에서는이를 표준화합니다. 하지만 때로는 성능상의 이유로 저수준 프리미티브 (예 : 기본 이중 체크 잠금 패턴 )를 원합니다. 새로운 표준은 뮤텍스 및 조건 변수와 같은 상위 가제트를 제공하며 원자 유형 및 다양한 메모리 장벽과 같은 저수준 가젯도 제공합니다. 이제 표준에 의해 지정된 언어로 정교하고 고성능의 동시 루틴을 작성할 수 있으며, 현재 시스템과 미래의 시스템 모두에서 코드가 변경되지 않고 컴파일되고 실행될 수 있습니다.

솔직히 말해서, 전문가이고 심각한 저수준 코드로 작업하지 않는 한, 뮤텍스와 조건 변수를 고수해야합니다. 그것이 내가하려는 의도입니다.

이 내용에 대한 자세한 내용은 이 블로그 게시물을 참조하십시오.





c# multithreading locking