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

비밀번호

댓글 작성자
 




[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13888정성태2/17/2025433닷넷: 2321. Blazor에서 발생할 수 있는 async void 메서드의 부작용
13887정성태2/17/2025395닷넷: 2320. Blazor의 razor 페이지에서 code-behind 파일로 코드를 분리하는 방법
13886정성태2/15/2025777VS.NET IDE: 196. Visual Studio - Code-behind처럼 cs 파일을 그룹핑하는 방법
13885정성태2/14/20251010닷넷: 2319. ASP.NET Core Web API / Razor 페이지에서 발생할 수 있는 async void 메서드의 부작용
13884정성태2/13/20251130닷넷: 2318. C# - (async Task가 아닌) async void 사용 시의 부작용파일 다운로드1
13883정성태2/12/20251099닷넷: 2317. C# - Memory Mapped I/O를 이용한 PCI Configuration Space 정보 열람파일 다운로드1
13882정성태2/10/20251097스크립트: 70. 파이썬 - oracledb 패키지 연동 시 Thin / Thick 모드
13881정성태2/7/20251174닷넷: 2316. C# - Port I/O를 이용한 PCI Configuration Space 정보 열람파일 다운로드1
13880정성태2/5/2025982오류 유형: 947. sshd - Failed to start OpenSSH server daemon.
13879정성태2/5/20251238오류 유형: 946. Ubuntu - N: Updating from such a repository can't be done securely, and is therefore disabled by default.
13878정성태2/3/20251083오류 유형: 945. Windows - 최대 절전 모드 시 DRIVER_POWER_STATE_FAILURE 발생 (pacer.sys)
13877정성태1/25/20251224닷넷: 2315. C# - PCI 장치 열거 (레지스트리, SetupAPI)파일 다운로드1
13876정성태1/25/20251404닷넷: 2314. C# - ProcessStartInfo 타입의 Arguments와 ArgumentList파일 다운로드1
13875정성태1/24/20251410스크립트: 69. 파이썬 - multiprocessing 패키지의 spawn 모드로 동작하는 uvicorn의 workers
13874정성태1/24/20251374스크립트: 68. 파이썬 - multiprocessing Pool의 기본 프로세스 시작 모드(spawn, fork)
13873정성태1/23/20251246디버깅 기술: 217. WinDbg - PCI 장치 열거
13872정성태1/23/20251244오류 유형: 944. WinDbg - 원격 커널 디버깅이 연결은 되지만 Break (Ctrl + Break) 키를 눌러도 멈추지 않는 현상
13871정성태1/22/20251329Windows: 278. Windows - 윈도우를 다른 모니터 화면으로 이동시키는 단축키 (Window + Shift + 화살표)
13870정성태1/18/20251679개발 환경 구성: 741. WinDbg - 네트워크 커널 디버깅이 가능한 NIC 카드 지원 확대
13869정성태1/18/20251702개발 환경 구성: 740. WinDbg - _NT_SYMBOL_PATH 환경 변수에 설정한 경로로 심벌 파일을 다운로드하지 않는 경우
13868정성태1/17/20251739Windows: 277. Hyper-V - Windows 11 VM의 Enhanced Session 모드로 로그인을 할 수 없는 문제
13867정성태1/17/20252031오류 유형: 943. Hyper-V에 Windows 11 설치 시 "This PC doesn't currently meet Windows 11 system requirements" 오류
13866정성태1/16/20251918개발 환경 구성: 739. Windows 10부터 바뀐 device driver 서명 방법
13865정성태1/15/20252065오류 유형: 942. C# - .NET Framework 4.5.2 이하의 버전에서 HttpWebRequest로 https 호출 시 "System.Net.WebException" 예외 발생
13864정성태1/15/20251997Linux: 114. eBPF를 위해 필요한 SELinux 보안 정책
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...