Microsoft MVP성태의 닷넷 이야기
닷넷: 2286. C# 13 - (3) Monitor를 대체할 Lock 타입 [링크 복사], [링크+제목 복사],
조회: 8459
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 9개 있습니다.)
닷넷: 2275. C# 13 - (1) 신규 이스케이프 시퀀스 '\e'
; https://www.sysnet.pe.kr/2/0/13673

닷넷: 2277. C# 13 - (2) 메서드 그룹의 자연 타입 개선 (메서드 추론 개선)
; https://www.sysnet.pe.kr/2/0/13681

닷넷: 2286. C# 13 - (3) Monitor를 대체할 Lock 타입
; https://www.sysnet.pe.kr/2/0/13699

닷넷: 2287. C# 13 - (4) Indexer를 이용한 개체 초기화 구문에서 System.Index 연산자 허용
; https://www.sysnet.pe.kr/2/0/13701

닷넷: 2291. C# 13 - (5) params 인자 타입으로 컬렉션 허용
; https://www.sysnet.pe.kr/2/0/13705

닷넷: 2294. C# 13 - (6) iterator 또는 비동기 메서드에서 ref와 unsafe 사용을 부분적으로 허용
; https://www.sysnet.pe.kr/2/0/13710

닷넷: 2303. C# 13 - (7) ref struct의 interface 상속 및 제네릭 제약으로 사용 가능
; https://www.sysnet.pe.kr/2/0/13752

닷넷: 2304. C# 13 - (8) 부분 메서드 정의를 속성 및 인덱서에도 확대
; https://www.sysnet.pe.kr/2/0/13754

닷넷: 2305. C# 13 - (9) 메서드 바인딩의 우선순위를 지정하는 OverloadResolutionPriority 특성 도입 (Overload resolution priority)
; https://www.sysnet.pe.kr/2/0/13755




C# 13 - (3) Monitor를 대체할 Lock 타입

지난 2개의 글에서 살펴본 것과는 달리 이번에는 Visual Studio 2022 버전과 .NET 9 SDK (Preview 6 이상)도 함께 설치해야 테스트를 할 수 있습니다.




C# 13부터, 새로운 유형의 잠금 방식이 추가됐습니다.

New lock object
; https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13#new-lock-object

[Proposal]: Lock statement pattern #7104
; https://github.com/dotnet/csharplang/issues/7104

신규 문법이라기보다는 .NET 9 BCL부터 새롭게 추가된 System.Threadking.Lock 타입에 기반한 동기화 방식인데요,

Lock Class
; https://learn.microsoft.com/en-us/dotnet/api/system.threading.lock?view=net-9.0

해당 타입은 대충 아래와 같은 명세를 지니는데,

public sealed class Lock
{
    public Lock();

    public bool IsHeldByCurrentThread { get; }

    public void Enter();
    public Scope EnterScope();
    public void Exit();
    public bool TryEnter();
    public bool TryEnter(int millisecondsTimeout);
    public bool TryEnter(TimeSpan timeout);

    public ref struct Scope
    {
        public void Dispose();
    }
}

기존에 쓰이던 System.Threadking.Monitor 타입과 비교해 보면,

public static class Monitor
{
    public static long LockContentionCount { get; }
    public static void Enter(object obj);
    public static void Enter(object obj, ref bool lockTaken);
    public static void Exit(object obj);
    public static bool IsEntered(object obj);
    // ...[생략]...
}

한 마디로, 정적으로 사용되는 유형이 인스턴스 유형으로 바뀌었다는 정도의 변화라고 보면 됩니다. 따라서 기존에는 Monitor.Enter()를 위해 별도의 object (또는, 참조형) 인스턴스가 필요했지만,

{
    object objLock = new();

    Monitor.Enter(objLock);
    // ... 공유 자원 read/write ...
    Monitor.Exit(objLock);
}

Lock 타입의 경우에는 자체 인스턴스가 내부에서 잠금 상태를 갖고 있어 그대로 사용하면 됩니다.

{
    System.Threading.Lock objLock = new();

    objLock.Enter();
    // ... 공유 자원 read/write ...
    objLock.Exit();
}

보는 바와 같이 사용법이 거의 유사하므로 기존 Monitor의 사용 경험을 그대로 살릴 수 있습니다. 또한, Monitor와의 차별점이라면 using 문에서도 사용할 수 있도록 별도의 EnterScope 메서드를 제공한다는 점입니다.

Lock lockObj = new();

using (lockObj.EnterScope())
{
    // ... 공유 자원 read/write ...
}

Lock 타입 자체는 IDisposable을 구현하지 않았지만, EnterScope 메서드가 반환하는 System.Threading.Lock.Scope 구조체는 IDisposable을 구현하고 있어 using 문과 자연스럽게 연동이 됩니다. 따라서 위의 코드는 실제 수행 시 다음과 같이 바뀝니다.

Lock lockObj = new();

System.Threading.Lock.Scope scope = lockObj.EnterScope();

try
{
    // ... 공유 자원 read/write 
} 
finally
{
    scope.Dispose();
}

Scope 구조체가 재미있는 점이 하나 있는데요, 바로 일반 구조체가 아닌 ref struct라는 점입니다. 따라서, EnterScope을 사용한다고 해서 GC Heap을 어지럽히는 일은 발생하지 않습니다.




마이크로소프트는 Monitor 대비 Lock 타입의 동기화 성능을 개선했고 개발자들로 하여금 향후 이것을 쓰라고 권고하고 있습니다. (기존의 lock + this 사용에 대한 부작용도 없습니다.) 그래서인지 최대한 언어에 통합을 시켰는데요, 가령 lock 예약어 구문에서도 대상 개체가 Lock 타입이면 그것과 연동해 코드를 생성합니다.

즉, 기존에는 object 인스턴스를 lock과 사용했지만,

{
    object objLock = new();
    lock (objLock)
    {
        // ... 공유 자원 read/write ...
    }
}

C# 13 컴파일러는 lock의 대상이 System.Threadking.Lock 개체인 경우,

{
    Lock lockObj = new();
    lock (lockObj) // C# 13 컴파일러는 대상 개체가 Lock 타입임을 인식
    {
        // ... 공유 자원 read/write ...
    }
}

자동으로 (개발자 대신) 그것의 EnterScope을 사용해 코드를 생성합니다.

{
    Lock lockObj = new();
    using (lockObj.EnterScope()) // 최종적으로는 try/finally의 Scope.Dispose()를 호출하는 구문으로 바뀜
    {
        // ... 공유 자원 read/write ...
    }
}

달리 말하면, 마이크로소프트는 신규 프로젝트뿐만 아니라, 기존 프로젝트의 동기화 코드도 가능한 "System.Threadking.Lock"으로 최대한 쉽게 마이그레이션할 수 있도록 나름 엄청 애를 쓴 것입니다.




정리하면, System.Threading.Lock을 사용하는 방법은 3가지 유형으로 가능한데,

  • Enter/Exit를 호출
  • using 문과 EnterScope를 사용
  • lock 예약어와 사용

개발자 입장에서는 (전에도 보통은 Monitor 대신 lock을 쓴 것처럼) 마지막 유형이 가장 편리할 것입니다.

기타, 그 외의 모든 면에서 System.Threading.Lock은 Monitor와 동일한 특성을 가지는데요, 가령 잠금 상태에서 STA COM 호출이나 일부 윈도우 메시지를 처리하는 것도 가능하고, 별도로 정리한 아래의 글에서 설명한 것처럼,

C# - async 메서드에서의 System.Threading.Lock 잠금 처리
; https://www.sysnet.pe.kr/2/0/13698

(WinForms/WPF SynchronizationContext가 제공되지 않는) async 메서드에서 사용할 수 없다는 것도 같습니다.

마지막으로, System.Threading.Lock은 단순히 BCL에 포함된 타입에 불과하기 때문에 그것을 직접 쓰는 것은 C# 12 이하에서도 가능합니다. 단지, lock 키워드와 연동하는 코드는,

// C# 12 이하에서 컴파일하는 경우
{
    Lock lockObj = new();
    lock (lockObj) // 분명히 Lock 타입도 참조형이지만 컴파일 오류 발생
    {
        Console.WriteLine("locked by object");
    }
}

컴파일 시 "error CS8652: The feature 'Lock object' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version." 오류가 발생합니다.




그나저나, 이번에도 야크 털 깎기(Yak Shaving)를 심하게 했군요. ^^; System.Threading.Lock의 도움말에 나온 아래의 문구 때문에,

Interrupt can interrupt threads that are waiting to enter a lock. On Windows STA threads, waits for locks allow message pumping that can run other code on the same thread during a wait.
...
A thread that enters a lock, including multiple times such as recursively, must exit the lock the same number of times to fully exit the lock and allow other threads to enter the lock.


테스트를 하고 싶어서 ATL 프로젝트가 필요해 실습하려다가 아래의 글이 나왔고,

Visual Studio - ATL Simple Object 추가 시 error C2065: 'IDR_...': undeclared identifier
; https://www.sysnet.pe.kr/2/0/13686

이후 C# 프로젝트에서 ATL COM 개체를 쓰려다 보니 또 정리하는 글이 필요해졌고,

개발 환경 구성: 717. Visual Studio - C# 프로젝트에서 레지스트리에 등록하지 않은 COM 개체 참조 및 사용 방법
; https://www.sysnet.pe.kr/2/0/13693

본격적으로 도움말의 문구를 테스트하다가 쓴 2개의 글과,

C# - Lock / Wait 상태에서도 일부 Win32 메시지 처리
; https://www.sysnet.pe.kr/2/0/13688

C# - Lock / Wait 상태에서도 STA COM 메서드 호출 처리
; https://www.sysnet.pe.kr/2/0/13695

메시지 처리를 살펴 본 김에 Win32 메시지 큐에 대한 조사를 하다가 정리한 글도 있고,

C# - PostThreadMessage로 보낸 메시지를 Windows Forms에서 수신하는 방법
; https://www.sysnet.pe.kr/2/0/13687

Windbg - 스레드의 Win32 Message Queue 정보 조회
; https://www.sysnet.pe.kr/2/0/13691

비동기 구문에서의 테스트를 위해 쓴 2개의 글까지 총 8개의 토픽을 둘러봐야 했습니다. (^^; 그야말로 한가한 티를 내는군요.)

C# - async 메서드에서의 lock/Monitor.Enter/Exit 잠금 처리
; https://www.sysnet.pe.kr/2/0/13697

C# - async 메서드에서의 System.Threading.Lock 잠금 처리
; https://www.sysnet.pe.kr/2/0/13698




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 8/6/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 121  [122]  123  124  125  126  127  128  129  130  131  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
10873정성태11/25/201525490.NET Framework: 540. C# - 부동 소수 계산 왜 이렇게 나오죠? (2) [3]파일 다운로드1
10872정성태11/24/201532470.NET Framework: 539. C# - 부동 소수 계산 왜 이렇게 나오죠? (1) [1]
10871정성태11/23/201528086오류 유형: 313. SignTool Error: No certificates were found that met all the given criteria.
10870정성태11/23/201528939오류 유형: 312. 윈도우 10 TH2 (버전 1511) 업데이트가 안되는 경우 [1]
10869정성태11/23/201524719오류 유형: 311. certutil 실행 오류 - 0x80070057 [1]
10868정성태11/20/201524597제니퍼 .NET: 25. 제니퍼 닷넷 적용 사례 (5) - RestSharp 라이브러리의 CPU High 현상파일 다운로드1
10867정성태10/18/201527157.NET Framework: 538. Thread.Abort로 인해 프로세스가 종료되는 현상
10866정성태10/14/201523359.NET Framework: 537. C# - Reflection의 박싱 없이 값 형식을 다루는 방법파일 다운로드1
10865정성태10/13/201523136.NET Framework: 536. Thread.Abort의 스레드 종료 지연파일 다운로드1
10864정성태10/12/201521342.NET Framework: 535. aspnet.config 파일의 설정을 읽는 방법
10863정성태10/9/201526154.NET Framework: 534. ASP.NET 응용 프로그램이 예외로 프로세스가 종료된다면?
10862정성태10/9/201524506오류 유형: 310. 비주얼 스튜디오 - Unspecified error (Exception from HRESULT: 0x80004005 (E_FAIL))
10861정성태10/9/201529159기타: 54. 도서: 시작하세요! C# 6.0 프로그래밍: 기본 문법부터 실전 예제까지 (2)
10860정성태10/5/201526605개발 환경 구성: 277. IIS AppPool의 시작/중단에 대한 이벤트 로그 확인 방법
10859정성태10/5/201527803.NET Framework: 533. C#에서 string 형식이 primitive일까요? [6]
10858정성태10/2/201524306VS.NET IDE: 105. Visual Studio의 단위 테스트 작성 시 Fakes를 이용한 메서드 재정의 방법 [1]파일 다운로드1
10857정성태10/1/201520337VS.NET IDE: 104. Visual C++ 프로젝트의 빌드 이벤트에서 환경 변수 사용하는 방법
10856정성태9/30/201531616.NET Framework: 532. WPF DataGrid의 데이터 바인딩 시 리플렉션의 부하는 어느 정도일까요?파일 다운로드1
10855정성태9/30/201521308.NET Framework: 531. C# - XSLT 내의 javascript에 전달한 XML 노드의 타입은?
10854정성태9/30/201521823오류 유형: 309. C# - 포인터를 쓰는 경우 VerificationException이 발생한다면?
10853정성태9/21/201519512오류 유형: 308. 공백 문자를 포함한 계정명의 권한으로 Visual Studio 확장을 설치할 때 오류 발생
10852정성태9/17/201524546VC++: 92. C++ 생성자를 DLL로부터 동적 로드해 객체를 생성한다면? [2]파일 다운로드1
10851정성태9/15/201524286.NET Framework: 530. C# - 중위식을 후위식으로 변환하는 예제파일 다운로드1
10850정성태9/14/201522951.NET Framework: 529. C# - volatile 키워드로 인한 차이점을 발생시키는 예제 [1]파일 다운로드1
10849정성태9/14/201557077오류 유형: 307. CLR20r3 오류 해결을 위해 mscorlib.dll을 덮어쓸때 주의할 점 [12]
10848정성태9/8/201527459VS.NET IDE: 103. Visual Studio의 Ctrl + F5 실행 동작파일 다운로드1
... 121  [122]  123  124  125  126  127  128  129  130  131  132  133  134  135  ...