다음을 통해 공유


Lock 개체

메모

이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.

기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 이러한 차이는 관련LDM(언어 디자인 모임) 노트에서 캡처됩니다.

사양문서에서 기능 스펙릿을 C# 언어 표준에 채택하는 과정에 대해 더 알아볼 수 있습니다.

챔피언 이슈: https://github.com/dotnet/csharplang/issues/7104

요약

System.Threading.Locklock 키워드와 어떻게 특수하게 상호 작용하는지를 설명합니다 (EnterScope 메서드를 내부적으로 자동 호출하여). 가능한 경우 형식의 실수로 오용되지 않도록 정적 분석 경고를 추가합니다.

동기

.NET 9는 기존 모니터 기반 잠금에 대한 더 나은 대안으로 새로운 System.Threading.Lock 형식 도입했습니다. C#에 lock 키워드가 있으면 개발자가 이 새 형식으로 사용할 수 있다고 생각할 수 있습니다. 이렇게 하면 이 형식의 의미 체계에 따라 잠기지 않고 대신 다른 개체로 처리하고 모니터 기반 잠금을 사용합니다.

namespace System.Threading
{
    public sealed class Lock
    {
        public void Enter();
        public void Exit();
        public Scope EnterScope();
    
        public ref struct Scope
        {
            public void Dispose();
        }
    }
}

상세 디자인

lock 문(§13.13)의 의미가 System.Threading.Lock 형식의 특정 사례로 변경됩니다.

형식 lock의 문서 lock (x) { ... }

  1. System.Threading.Lock형식의 표현인 x을 포함하는 은(는), 정확히와(과) 동일하다.
    using (x.EnterScope())
    {
        ...
    }
    
    System.Threading.Lock의 형태를 가져야 합니다.
    namespace System.Threading
    {
        public sealed class Lock
        {
            public Scope EnterScope();
    
            public ref struct Scope
            {
                public void Dispose();
            }
        }
    }
    
  2. xreference_type의 표현으로, 정확히 다음과 같습니다: [...]

셰이프가 완전히 확인되지 않을 수 있습니다(예: Lock 형식이 sealed않은 경우 오류나 경고가 없음). 하지만 기능이 예상대로 작동하지 않을 수 있습니다(예: 기능이 파생 형식이 없다고 가정하므로 Lock 파생 형식으로 변환할 때 경고가 발생하지 않음).

또한 System.Threading.Lock 형식을 업캐스트할 때 암시적 참조 변환(§10.2.8)에 새 경고가 추가됩니다.

암시적 참조 변환은 다음과 같습니다.

  • 어떤 reference_type에서 objectdynamic로.
    • 참조 유형이(가) System.Threading.Lock로 알려졌을 때 경고가 보고됩니다.
  • class_typeS에서 class_typeT로, 단 ST에서 파생된 경우에만 가능합니다.
    • SSystem.Threading.Lock인 것으로 알려지면 경고가 보고됩니다.
  • 어떤 class_typeS 에서 어떤 interface_typeT로, ST을 구현하는 경우에 가능합니다.
    • SSystem.Threading.Lock인 것으로 알려지면 경고가 보고됩니다.
  • [...]
object l = new System.Threading.Lock(); // warning
lock (l) { } // monitor-based locking is used here

이 경고는 동등한 명시적 변환에도 발생합니다.

컴파일러는 object변환한 후 인스턴스를 잠글 수 없는 경우에 경고를 보고하지 않습니다.

  • 변환이 암시적이고 개체 같음 연산자 호출의 일부인 경우
var l = new System.Threading.Lock();
if (l != null) // no warning even though `l` is implicitly converted to `object` for `operator!=(object, object)`
    // ...

경고를 회피하고 모니터 기반 잠금을 강제로 사용하려면 다음을 사용할 수 있습니다.

  • 일반적인 경고 억제 수단(#pragma warning disable)
  • Monitor API를 직접
  • object AsObject<T>(T l) => (object)l;같은 간접 캐스팅.

대안

  • 다른 형식이 lock 키워드와 상호 작용하는 데 사용할 수 있는 일반적인 패턴을 지원합니다. 이는 ref struct제네릭에 참여할 수 있을 때 구현될 수 있는 향후 작업입니다. LDM 2023-12-04에서 논의했습니다.

  • 기존 모니터 기반 잠금과 새 Lock(또는 향후 패턴) 간의 모호성을 방지하기 위해 다음을 수행할 수 있습니다.

    • 기존 lock 문을 다시 사용하는 대신 새 구문을 소개합니다.
    • 새 잠금 형식을 struct로 지정해야 합니다 (기존 lock은 값 유형을 허용하지 않음). 구조체에 지연 초기화가 있는 경우 기본 생성자 및 복사에 문제가 있을 수 있습니다.
  • codegen은 더 이상 사용되지 않는 스레드 중단에 대해 강력해질 수 있습니다.

  • 형식 매개 변수에 대한 잠금은 항상 모니터 기반 잠금을 사용하므로 Lock 형식 매개 변수로 전달되는 경우에도 경고할 수 있습니다.

    M(new Lock()); // could warn here
    
    void M<T>(T x) // (specifying `where T : Lock` makes no difference)
    {
        lock (x) { } // because this uses Monitor
    }
    

    그러나 바람직하지 않은 목록에 Lock저장할 때 경고가 발생합니다.

    List<Lock> list = new();
    list.Add(new Lock()); // would warn here
    
  • usingawait에서 System.Threading.Lock 사용을 방지하기 위해 정적 분석을 포함할 수 있습니다. 즉, using (lockVar.EnterScope()) { await ... }같은 코드에 대해 오류나 경고를 발생시킬 수 있습니다. 현재는 Lock.Scoperef struct이기 때문에 해당 코드는 어차피 불법이어서 필요하지 않습니다. 그러나 ref structasync 메서드에 허용하거나 Lock.Scoperef struct이 되지 않도록 변경한 경우, 이 분석이 유익해질 것입니다. 또한 나중에 구현되는 경우 모든 잠금 형식이 일반 패턴과 일치하는지를 고려해야 할 가능성이 있습니다. 일부 잠금 형식은 await과 함께 사용할 수 있으므로, 이를 위한 옵트아웃 메커니즘이 필요할 수 있습니다.) 또는, 이는 런타임의 일부로 제공되는 분석기로 구현할 수 있습니다.

  • 값 형식을 lock수 없다는 제한을 완화할 수 있습니다.

    • Lock 형식의 경우(API 제안이 class에서 struct로 변경된 경우에만 필요)
    • 는 나중에 구현될 때 모든 형식이 참여할 수 있는 일반 패턴입니다.
  • lock내에서 await 사용되지 않는 async 메서드에서 새 lock 허용할 수 있습니다.

    • 현재 ref struct를 리소스로 사용하여 lockusing로 낮춰지면서 컴파일 시 오류가 발생합니다. 해결 방법은 lock 별도의 비async 메서드로 추출하는 것입니다.
    • ref struct Scope사용하는 대신 try/finallyLock.EnterLock.Exit 메서드를 내보낸다. 그러나 Exit 메서드는 Enter과 다른 스레드에서 호출될 경우 반드시 예외를 던져야 하므로, 이러한 상황을 처리하기 위해 스레드 조회를 포함하며, 이는 Scope를 사용하면 방지됩니다.
    • using 본문 내에 await이 없는 경우, async 메서드에서 ref struct을 사용하여 using을 컴파일하는 것이 가장 좋습니다.

디자인 회의

  • LDM 2023-05-01: lock 패턴을 지원하기 위한 초기 결정
  • LDM 2023-10-16: .NET 9의 작업 집합에 포함됨
  • LDM 2023-12-04: 일반 패턴을 거부하고, Lock 유형에 대한 특수 처리를 허용하며 정적 분석 경고를 추가함.