Microsoft MVP성태의 닷넷 이야기
닷넷: 2286. C# 13 - (3) Monitor를 대체할 Lock 타입 [링크 복사], [링크+제목 복사],
조회: 8497
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 76  77  78  79  80  81  82  [83]  84  85  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
11862정성태4/7/201920170개발 환경 구성: 437. .NET EXE의 ASLR 기능을 끄는 방법
11861정성태4/6/201919628디버깅 기술: 126. windbg - .NET x86 CLR2/CLR4 EXE의 EntryPoint
11860정성태4/5/201923487오류 유형: 527. Visual C++ 컴파일 오류 - error C2220: warning treated as error - no 'object' file generated
11859정성태4/4/201920708디버깅 기술: 125. WinDbg로 EXE의 EntryPoint에서 BP 거는 방법
11858정성태3/27/201921621VC++: 129. EXE를 LoadLibrary로 로딩해 PE 헤더에 있는 EntryPoint를 직접 호출하는 방법파일 다운로드1
11857정성태3/26/201919524VC++: 128. strncpy 사용 시 주의 사항(Linux / Windows)
11856정성태3/25/201919774VS.NET IDE: 134. 마이크로소프트의 CoreCLR 프로파일러 리눅스 예제를 Visual Studio F5 원격 디버깅하는 방법 [1]파일 다운로드1
11855정성태3/25/201921941개발 환경 구성: 436. 페이스북 HTTPS 인증을 localhost에서 테스트하는 방법
11854정성태3/25/201917589VS.NET IDE: 133. IIS Express로 호스팅하는 사이트를 https로 접근하는 방법
11853정성태3/24/201920396개발 환경 구성: 435. 존재하지 않는 IP 주소에 대한 Dns.GetHostByAddress/gethostbyaddr/GetNameInfoW 실행이 느리다면? - 두 번째 이야기 [1]
11852정성태3/20/201919616개발 환경 구성: 434. 존재하지 않는 IP 주소에 대한 Dns.GetHostByAddress/gethostbyaddr/GetNameInfoW 실행이 느리다면?파일 다운로드1
11851정성태3/19/201923362Linux: 8. C# - 리눅스 환경에서 DllImport 대신 라이브러리 동적 로드 처리 [2]
11850정성태3/18/201922416.NET Framework: 813. C# async 메서드에서 out/ref/in 유형의 인자를 사용하지 못하는 이유
11849정성태3/18/201921773.NET Framework: 812. pscp.exe 기능을 C#으로 제어하는 방법파일 다운로드1
11848정성태3/17/201918510스크립트: 14. 윈도우 CMD - 파일이 변경된 경우 파일명을 변경해 복사하고 싶다면?
11847정성태3/17/201922977Linux: 7. 리눅스 C/C++ - 공유 라이브러리 동적 로딩 후 export 함수 사용 방법파일 다운로드1
11846정성태3/15/201921626Linux: 6. getenv, setenv가 언어/운영체제마다 호환이 안 되는 문제
11845정성태3/15/201921763Linux: 5. Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency) [3]
11844정성태3/14/201923089개발 환경 구성: 434. Visual Studio 2019 - 리눅스 프로젝트를 이용한 공유/실행(so/out) 프로그램 개발 환경 설정 [1]파일 다운로드1
11843정성태3/14/201918038기타: 75. MSDN 웹 사이트를 기본으로 영문 페이지로 열고 싶다면?
11842정성태3/13/201916394개발 환경 구성: 433. 마이크로소프트의 CoreCLR 프로파일러 예제를 Visual Studio CMake로 빌드하는 방법 [1]파일 다운로드1
11841정성태3/13/201916696VS.NET IDE: 132. Visual Studio 2019 - CMake의 컴파일러를 기본 g++에서 clang++로 변경
11840정성태3/13/201918320오류 유형: 526. 윈도우 10 Ubuntu App 환경에서는 USB 외장 하드 접근 불가
11839정성태3/12/201922255디버깅 기술: 124. .NET Core 웹 앱을 호스팅하는 Azure App Services의 프로세스 메모리 덤프 및 windbg 분석 개요 [3]
11838정성태3/7/201925882.NET Framework: 811. (번역글) .NET Internals Cookbook Part 1 - Exceptions, filters and corrupted processes [1]파일 다운로드1
11837정성태3/6/201939812기타: 74. 도서: 시작하세요! C# 7.3 프로그래밍 [10]
... 76  77  78  79  80  81  82  [83]  84  85  86  87  88  89  90  ...