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

비밀번호

댓글 작성자
 




... 136  137  138  139  140  [141]  142  143  144  145  146  147  148  149  150  ...
NoWriterDateCnt.TitleFile(s)
1530정성태11/5/201327460기타: 38. 오픈소스로 풀린 하드 디스크 관리 도구 - WindowSMART
1529정성태11/5/201323349오류 유형: 192. SQL 서버 - The transaction log for database '...' is full due to 'LOG_BACKUP'.
1528정성태11/5/201328943디버깅 기술: 58. windbg 분석 사례 - WPF 응용 프로그램의 UI가 반응하지 않는 문제 [5]
1527정성태11/4/201326557VC++: 72. error MIDL2311 - mktyplib compatability mode 컴파일 오류
1526정성태11/3/201323263디버깅 기술: 57. C# - double 값에 대한 windbg 확인
1525정성태11/2/201329658.NET Framework: 391. C# - EXE/DLL로부터 추출한 이미지/아이콘의 배경색 투명 처리 [8]
1524정성태11/2/201330494기타: 37. 프로그램에 보여지는 리소스(예: 아이콘) 추출하는 방법 [1]
1523정성태11/2/201326857VS.NET IDE: 81. Visual Studio 확장 도구 AttachToW3WP - w3wp.exe에 대한 디버거 연결을 자동화하는 도구 [2]
1522정성태11/1/201323443VS.NET IDE: 80. IIS 8.0/8.5 - Global.asax.cs처럼 초기에 실행되는 코드에 Breakpoint를 잡는 방법
1521정성태11/1/201329304VS.NET IDE: 79. IIS 7.5 - Global.asax.cs처럼 초기에 실행되는 코드에 Breakpoint를 잡는 방법
1520정성태10/31/201323713오류 유형: 191. Visual Studio 2010 - 웹 애플리케이션 생성 시 "The project type is not supported by this installation." 오류 발생 해결
1519정성태10/31/201349234기타: 36. SYSTEM 또는 TrustedInstaller 소유로 되어 있는 폴더/파일을 삭제하는 방법 [5]
1518정성태10/30/201326890VS.NET IDE: 78. Visual Studio 확장으로 XmlCodeGenerator 제작하는 방법
1517정성태10/28/201326462디버깅 기술: 56. 덤프 파일에 핸들/스레드 정보를 포함하는 방법 [1]
1516정성태10/28/201331792.NET Framework: 390. FolderBrowserDialog보다 더 쓸만한 대화창이 필요하다면? [1]
1515정성태10/24/201334467VS.NET IDE: 77. Visual Studio 확장(VSIX) 만드는 방법 [5]
1514정성태10/24/201367804개발 환경 구성: 202. Internet Explorer 11을 7, 8, 9, 10 버전으로 인식시키는 방법 [9]파일 다운로드1
1513정성태10/23/201324357개발 환경 구성: 201. Azure Blob Storage의 DNS 경로를 사용자 DNS로 바꾸는 방법 [1]
1512정성태10/18/201327576개발 환경 구성: 200. IIS AppPool의 실행 계정을 변경하는 방법
1511정성태10/12/201325718.NET Framework: 389. The 3n + 1 problem의 C#/Java 버전 풀이 [2]
1510정성태10/8/201326615오류 유형: 190. 윈도우 서버 2012 R2 설치 후 인텔 NIC으로 인한 WMI 오류 발생
1509정성태10/8/201331791오류 유형: 189. Windows Server 8.1/2012 R2 - IME 비정상 종료 현상 [1]
1508정성태10/4/201326864.NET Framework: 388. 일반 닷넷 프로젝트에서 WinRT API를 호출하는 방법 [2]파일 다운로드1
1507정성태9/30/201324725오류 유형: 188. The key 'LocalizedPerfCounter' does not exist in the appSettings configuration section.
1506정성태9/30/201326903오류 유형: 187. Parameter "basePath" cannot be a relative path
1505정성태9/26/201375389기타: 35. Microsoft Office 2007 인증 생략하는 방법 [10]
... 136  137  138  139  140  [141]  142  143  144  145  146  147  148  149  150  ...