메소드를 스레드로부터 안전하게 만드는 것은 무엇입니까? 규칙은 무엇입니까?


156

메소드를 스레드로부터 안전하게 만드는 것에 대한 전반적인 규칙 / 지침이 있습니까? 일회성 상황이 백만 건일 수 있지만 일반적으로 어떻습니까? 이것이 간단합니까?

  1. 메소드가 로컬 변수에만 액세스하는 경우 스레드 안전합니다.

그게 다야? 정적 메소드에도 적용됩니까?

@Cybis가 제공 한 한 가지 대답은 다음과 같습니다.

각 스레드는 자체 스택을 가져 오기 때문에 스레드간에 로컬 변수를 공유 할 수 없습니다.

정적 메소드도 마찬가지입니까?

메소드에 참조 객체가 전달되면 스레드 안전성이 손상됩니까? 나는 약간의 연구를 해왔고, 어떤 경우에 대해서는 많은 것이 있지만, 몇 가지 규칙을 사용하여 방법이 스레드 안전하다는 것을 보장하기 위해 따라야 할 지침을 정의 할 수 있기를 바랐습니다.

따라서 궁극적 인 질문은 "스레드 안전 방법을 정의하는 짧은 규칙 목록이 있습니까? 그렇다면 그렇다면 무엇입니까?"입니다.

편집
여기에는 많은 좋은 점이 있습니다. 이 질문에 대한 진정한 대답은 "스레드 안전을 보장하는 간단한 규칙은 없습니다"라고 생각합니다. 멋있는. 좋아. 그러나 일반적으로 나는 받아 들인 대답이 짧고 좋은 요약을 제공한다고 생각합니다. 항상 예외가 있습니다. 그러면 그렇게 해. 나는 그걸로 살 수 있습니다.


59
잠금없이 다른 스레드에서도 액세스 할 수있는 변수에는 액세스 할 수 없습니다.
Hans Passant

4
한스의 마음이 굳어졌다!
Martin James

3
또한 .. '당신은 잠금없이 다른 쓰레드에 의해서도 접근 할 수있는 변수에 접근해서는 안된다.'-읽은 값이 때때로 최신이 아니거나 실제로 심각하지 않은 것이 중요하다.
Martin James

여기 에 에릭이 당신을 회오리 바람으로 데려다 줄 멋진 블로그가 있습니다.
RBT

답변:


139

메소드 (인스턴스 또는 정적)가 해당 메소드 내에서 범위가 지정된 변수 만 참조하는 경우 각 스레드에는 자체 스택이 있으므로 스레드 안전합니다.

이 경우 여러 스레드가 ThreadSafeMethod문제없이 동시에 호출 될 수 있습니다.

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number; // each thread will have its own variable for number.
        number = parameter1.Length;
        return number;
    }
}

메소드가 로컬 범위 변수 만 참조하는 다른 클래스 메소드를 호출하는 경우에도 마찬가지입니다.

public class Thing
{
    public int ThreadSafeMethod(string parameter1)
    {
        int number;
        number = this.GetLength(parameter1);
        return number;
    }

    private int GetLength(string value)
    {
        int length = value.Length;
        return length;
    }
}

메소드가 (객체 상태) 속성 또는 필드 (인스턴스 또는 정적)에 액세스하는 경우 다른 스레드에서 값을 수정하지 않도록 잠금을 사용해야합니다.

public class Thing
{
    private string someValue; // all threads will read and write to this same field value

    public int NonThreadSafeMethod(string parameter1)
    {
        this.someValue = parameter1;

        int number;

        // Since access to someValue is not synchronised by the class, a separate thread
        // could have changed its value between this thread setting its value at the start 
        // of the method and this line reading its value.
        number = this.someValue.Length;
        return number;
    }
}

구조체 나 불변이 아닌 메서드에 전달 된 모든 매개 변수는 메서드 범위 밖의 다른 스레드에 의해 변경 될 수 있습니다.

적절한 동시성을 보장하려면 잠금을 사용해야합니다.

자세한 정보는 잠금 문 C # 참조ReadWriterLockSlim을 참조 하십시오 .

lock 은 대부분 한 번에 하나의 기능을 제공하는 데 유용하며
ReadWriterLockSlim여러 독자와 단일 작성자가 필요한 경우 유용합니다.


15
세 번째 예 private string someValue;에서는 그렇지 static않으므로 각 인스턴스는 해당 변수의 별도 사본을 얻습니다. 그렇다면 이것이 스레드 안전이 아닌 방법을 설명해 주시겠습니까?
Bharadwaj

29
Thing여러 스레드가 액세스 하는 클래스의 인스턴스가 하나 인 경우
@Bharadwaj

111

메소드가 로컬 변수에만 액세스하는 경우 스레드 안전합니다. 그게 다야?

절대로 아닙니다. 그럼에도 불구하고 스레드 안전하지 않은 단일 스레드에서 액세스하는 단일 로컬 변수로 프로그램을 작성할 수 있습니다.

https://stackoverflow.com/a/8883117/88656

정적 메소드에도 적용됩니까?

절대적으로하지.

@Cybis가 제공 한 한 가지 대답은 "각 스레드가 자체 스택을 가져 오기 때문에 로컬 변수를 스레드간에 공유 할 수 없습니다."

절대적으로하지. 로컬 변수의 특징은 임시 풀에 할당 된 것이 아니라 로컬 범위 내에서만 볼 수 있다는 것입니다 . 완벽하게 합법적이며 두 개의 다른 스레드에서 동일한 로컬 변수에 액세스 할 수 있습니다. 익명 메소드, 람다, 반복자 블록 또는 비동기 메소드를 사용하여이를 수행 할 수 있습니다.

정적 메소드도 마찬가지입니까?

절대적으로하지.

메소드에 참조 객체가 전달되면 스레드 안전성이 손상됩니까?

아마도.

나는 약간의 연구를 해왔고, 어떤 경우에 대해서는 많은 것이 있지만, 몇 가지 규칙을 사용하여 방법이 스레드 안전하다는 것을 보장하기 위해 따라야 할 지침을 정의 할 수 있기를 바랐습니다.

실망으로 사는 법을 배워야 할 것입니다. 이것은 매우 어려운 주제입니다.

그래서 궁극적 인 질문은 다음과 같습니다. "스레드 안전 방법을 정의하는 짧은 규칙 목록이 있습니까?

아니. 이전 예제에서 보았 듯이 빈 메소드는 스레드로부터 안전하지 않을 수 있습니다 . "방법이 올바른지 확인 하는 짧은 규칙 목록이 있습니까? " 아니 없어. 스레드 안전성은 매우 복잡한 종류의 정확성에 지나지 않습니다.

또한 질문을한다는 사실은 나사산 안전에 대한 근본적인 오해를 나타냅니다. 스레드 안전성은 프로그램 의 로컬 속성이 아닌 글로벌 입니다. 올바르게 얻기가 어려운 이유 는 안전을 위해 전체 프로그램의 스레딩 동작에 대한 완전한 지식이 있어야 하기 때문 입니다.

다시 한 번, 내 예를 살펴보십시오. 모든 방법은 사소 합니다. 프로그램이 교착 상태가되는 "전역"레벨에서 메소드가 서로 상호 작용하는 방식입니다. 모든 방법을보고 "안전한"것으로 확인한 다음 전체 프로그램이 안전하다고 기대할 수는 없습니다. 중공이 아닙니다. 집의 빈 공간은 모든 부분의 속성이 아니라 전체의 세계적인 속성입니다.


14
핵심 진술 : 스레드 안전성은 프로그램의 로컬 속성이 아니라 글로벌입니다.
Greg D

5
@BobHorn : class C { public static Func<int> getter; public static Action<int> setter; public static void M() { int x = 0; getter = ()=>x; setter = y=>{x=y;};} } M ()을 호출 한 다음 두 개의 다른 스레드에서 C.getter 및 C.setter를 호출하십시오. 이제 로컬 변수 인 경우에도 두 개의 다른 스레드에서 로컬 변수를 쓰고 읽을 수 있습니다. 다시 말하지만, 지역 변수의 정의 특성은 그것이 변수가 스레드의 스택에있는 것이 아니라 지역 이라는 것입니다 .
Eric Lippert

3
@BobHorn : 권위와 지식으로 도구에 대해 이야기 할 수있는 수준에서 도구를 이해하는 것이 더 중요하다고 생각합니다. 예를 들어, 원래의 질문은 지역 변수가 무엇인지에 대한 이해 부족을 배신했습니다. 이 오류는 매우 흔하지 만 실제로는 오류이므로 수정해야합니다. 지역 변수의 정의는 매우 정확하며 그에 따라 처리해야합니다. :) 이것은 병리학 적 사례가 존재하지 않는 "사람"에게는 답이 아닙니다. 이것들은 당신이 실제로 요청한 것을 이해할 수 있도록 설명입니다. :)
Greg D

7
@EricLippert : 많은 클래스에 대한 MSDN 설명서는 '이 유형의 Public static (Visual Basic에서 공유) 멤버는 스레드로부터 안전합니다. 모든 인스턴스 멤버는 스레드 안전을 보장하지 않습니다. ' 또는 때때로 '이 유형은 스레드 안전합니다.' (예 : 문자열) 스레드 안전성이 전 세계적으로 중요 할 때 어떻게 이러한 보장을 할 수 있습니까?
Mike Zboray

3
@ mikez : 좋은 질문입니다. 이러한 메소드는 상태를 변경하지 않거나 잠금을 사용하지 않고 상태를 안전하게 변경하거나 잠금을 사용하는 경우 전역 "잠금 순서"를 위반하지 않는 방식으로 변경합니다. 문자열은 메서드가 변경되지 않는 변경 불가능한 데이터 구조이므로 문자열을 쉽게 안전하게 만들 수 있습니다.
Eric Lippert

11

단단하고 빠른 규칙은 없습니다.

다음은 .NET에서 코드 스레드를 안전하게 만드는 몇 가지 규칙과 왜 이것이 좋은 규칙이 아닌지입니다.

  1. 함수와 함수가 호출하는 모든 함수는 순수해야하며 (부작용 없음) 지역 변수를 사용해야합니다. 이렇게하면 코드를 스레드로부터 안전하게 만들 수 있지만 .NET에서는이 제한으로 수행 할 수있는 흥미로운 작업이 거의 없습니다.
  2. 공통 객체에서 작동하는 모든 기능 lock은 공통적 인 것이 어야합니다 . 모든 잠금은 동일한 순서로 수행해야합니다. 이렇게하면 코드 스레드가 안전 해지지 만 엄청나게 느려질 수 있으며 여러 스레드를 사용하지 않을 수도 있습니다.
  3. ...

코드 스레드를 안전하게 만드는 규칙은 없으며, 수행 할 수있는 유일한 일은 코드가 실제로 실행되는 횟수에 관계없이 코드가 작동하는지 확인하는 것입니다. 각 스레드는 어느 시점에서든 중단 될 수 있습니다. 자체 상태 / 위치 및 공통 객체에 액세스하는 각 기능 (정적 또는 기타)에 대해


10
잠금 속도가 느릴 필요는 없습니다. 자물쇠는 엄청나게 빠릅니다. 비경쟁 잠금은 10에서 100 나노초 정도의 크기입니다 . 경쟁 잠금은 물론 속도가 느립니다. 잠금에 대해 이의를 제기하여 속도가 느려 지면 경합을 제거하기 위해 프로그램을 재구성하십시오 .
Eric Lippert

2
# 2를 충분히 명확하게하지 못했습니다. 스레드를 안전하게 만드는 방법이 있다고 말하려고했습니다. 공통 객체에 대한 각 액세스 주위에 자물쇠를 두십시오. 그리고 여러 스레드가 있다고 가정하면 경합이 발생하고 잠금으로 인해 전체 속도가 느려집니다. 자물쇠를 추가 할 때, 맹목적으로 자물쇠를 추가 할 수 없으며, 아주 좋은 전략적 장소에 배치해야합니다.
earlNameless

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.