Microsoft MVP성태의 닷넷 이야기
닷넷: 2286. C# 13 - (3) Monitor를 대체할 Lock 타입 [링크 복사], [링크+제목 복사],
조회: 4467
글쓴 사람
정성태 (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)
13512정성태1/4/20246318개발 환경 구성: 701. IIS - w3wp.exe 프로세스의 ASP.NET 런타임을 항상 Warmup 모드로 유지하는 preload Enabled 설정
13511정성태1/4/20246280닷넷: 2193. C# - ASP.NET Web Application + OpenAPI(Swashbuckle) 스펙 제공
13510정성태1/3/20245979닷넷: 2192. C# - 특정 실행 파일이 있는지 확인하는 방법 (Linux)
13509정성태1/3/20246951오류 유형: 887. .NET Core 2 이하의 프로젝트에서 System.Runtime.CompilerServices.Unsafe doesn't support netcoreapp2.0.
13508정성태1/3/20246837오류 유형: 886. ORA-28000: the account is locked
13507정성태1/2/20247649닷넷: 2191. C# - IPGlobalProperties를 이용해 netstat처럼 사용 중인 Socket 목록 구하는 방법파일 다운로드1
13506정성태12/29/20236971닷넷: 2190. C# - 닷넷 코어/5+에서 달라지는 System.Text.Encoding 지원
13505정성태12/27/20238202닷넷: 2189. C# - WebSocket 클라이언트를 닷넷으로 구현하는 예제 (System.Net.WebSockets)파일 다운로드1
13504정성태12/27/20237235닷넷: 2188. C# - ASP.NET Core SignalR로 구현하는 채팅 서비스 예제파일 다운로드1
13503정성태12/27/20236809Linux: 67. WSL 환경 + mlocate(locate) 도구의 /mnt 디렉터리 검색 문제
13502정성태12/26/20237112닷넷: 2187. C# - 다른 프로세스의 환경변수 읽는 예제파일 다운로드1
13501정성태12/25/20236794개발 환경 구성: 700. WSL + uwsgi - IPv6로 바인딩하는 방법
13500정성태12/24/20236960디버깅 기술: 194. Windbg - x64 가상 주소를 물리 주소로 변환
13498정성태12/23/20238449닷넷: 2186. 한국투자증권 KIS Developers OpenAPI의 C# 래퍼 버전 - eFriendOpenAPI NuGet 패키지
13497정성태12/22/20236581오류 유형: 885. Visual Studiio - error : Could not connect to the remote system. Please verify your connection settings, and that your machine is on the network and reachable.
13496정성태12/21/20237292Linux: 66. 리눅스 - 실행 중인 프로세스 내부의 환경변수 설정을 구하는 방법 (gdb)
13495정성태12/20/20237214Linux: 65. clang++로 공유 라이브러리의 -static 옵션 빌드가 가능할까요?
13494정성태12/20/20237482Linux: 64. Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency) - 두 번째 이야기
13493정성태12/19/20237434닷넷: 2185. C# - object를 QueryString으로 직렬화하는 방법
13492정성태12/19/20236846개발 환경 구성: 699. WSL에 nopCommerce 예제 구성
13491정성태12/19/20235939Linux: 63. 리눅스 - 다중 그룹 또는 사용자를 리소스에 권한 부여
13490정성태12/19/20236202개발 환경 구성: 698. Golang - GLIBC 의존을 없애는 정적 빌드 방법
13489정성태12/19/20236386개발 환경 구성: 697. GoLand에서 ldflags 지정 방법
13488정성태12/18/20236164오류 유형: 884. HTTP 500.0 - 명령행에서 실행한 ASP.NET Core 응용 프로그램을 실행하는 방법
13487정성태12/16/20236621개발 환경 구성: 696. C# - 리눅스용 AOT 빌드를 docker에서 수행 [1]
13486정성태12/15/20236389개발 환경 구성: 695. Nuget config 파일에 값 설정/삭제 방법
1  2  3  4  5  6  7  8  9  10  11  12  13  [14]  15  ...