문제

변수 "카운터"가 있다고 가정하고 인터 로크를 사용하여 "카운터"값에 액세스하고 설정하는 몇 가지 스레드가 있습니다.

int value = Interlocked.Increment(ref counter);

그리고

int value = Interlocked.Decrement(ref counter);

인터 로크에 의한 변경 사항이 모든 스레드에서 볼 수 있다고 가정 할 수 있습니까?

그렇지 않은 경우 모든 스레드가 변수를 동기화하려면 어떻게해야합니까?

편집 : 누군가가 휘발성을 사용하도록 제안했습니다. 그러나 "카운터"를 휘발성으로 설정하면 컴파일러 경고가 있습니다. "휘발성 필드에 대한 참조는 휘발성으로 취급되지 않습니다."

온라인 도움말을 읽을 때 "휘발성 필드는 일반적으로 Ref 또는 Out 매개 변수를 사용하여 전달되어서는 안됩니다"라고 말했습니다.

도움이 되었습니까?

해결책

X86 CPU (X86의 Lock Add/DEC)의 InterlockEdIncrement/감소 메모리 장벽 모든 스레드에 가시성을 부여합니다 (즉, 모든 스레드는 순차적 메모리 일관성과 같이 업데이트가 사역으로 볼 수 있습니다). 메모리 배리어는 모든 보류중인 메모리 부하/저장소를 완성 할 수 있도록합니다. volatile C# 및 Java (및 일부 C/C ++ 컴파일러)가 시행되지만이 질문과 관련이 없습니다. volatile 메모리 장벽을 만들기 위해. 그러나 인터 로크 작업에는 이미 CPU의 메모리 장벽이 있습니다.

살펴보세요 나의 또 다른 대답 stackoverflow에서.

C#의 인터 로크 시드 크레멘트/감소는 X86의 잠금 추가/dec에 대한 본질적인 매핑이라고 가정합니다.

다른 팁

인터 로크에 의한 변경 사항이 모든 스레드에서 볼 수 있다고 가정 할 수 있습니까?

이것은 값을 읽는 방법에 따라 다릅니다. 당신이 그것을 "그냥"만 읽으면, 아니요, 휘발성으로 표시하지 않으면 다른 스레드에서 항상 보이지 않습니다. 그래도 성가신 경고를 유발합니다.

대안 (그리고 선호하는 IMO)으로서 다른 인터 로크 명령을 사용하여 읽으십시오. 이것은 항상 모든 스레드에서 업데이트 된 값을 볼 수 있습니다.

int readvalue = Interlocked.CompareExchange(ref counter, 0, 0);

값을 읽는 값을 반환하고 0 인 경우 0으로 스왑합니다.

동기 부여 : 경고는 무언가가 옳지 않다는 것을 암시합니다. 두 기술 (휘발성 및 인터 로크)을 결합하는 것은이를 수행하는 의도 된 방법이 아닙니다.

업데이트 : "휘발성"을 사용하지 않고 신뢰할 수있는 32 비트 읽기에 대한 또 다른 접근 방식은 사용하는 것 같습니다. Thread.VolatileRead 제안 된 바와 같이 이 답변. 내가 사용하는 것에 대해 내가 완전히 틀렸다는 증거도 있습니다. Interlocked 예를 들어 32 비트 읽기의 경우 이 연결 문제, 비록 나는 차이가 본질적으로 약간 멍청한 지 궁금합니다.

내가 정말로 의미하는 바는 다음과 같습니다.이 답변을 유일한 출처로 사용하지 마십시오. 나는 이것에 대한 의심을 가지고있다.

사실, 그들은 그렇지 않습니다. 안전하게 수정하려면 counter, 당신은 올바른 일을하고 있습니다. 그러나 당신이 읽고 싶다면 counter 직접 선언해야합니다 volatile. 그렇지 않으면 컴파일러는 counter 이 때문에 변경됩니다 Interlocked 작업은 볼 수없는 코드에 있습니다.

인터 로크는 한 번에 1 스레드 만 값을 업데이트 할 수 있도록합니다. 다른 스레드가 올바른 값 (캐시 된 값이 아닌)을 읽을 수 있도록 휘발성으로 표시하십시오.

공개 휘발성 INT 카운터;

아니; an 연락을 취한 연락 만하면됩니다 ~ 아니다 코드의 변수 읽기가 실제로 신선한 지 확인하십시오. 필드에서 올바르게 읽지 않는 프로그램 스레드 안전이 아닐 수도 있습니다, "강한 메모리 모델"에서도. 이는 스레드 사이에 공유되는 필드에 할당 모든 형태에 적용됩니다.

다음은 결코 종료되지 않는 코드의 예입니다. JIT 때문에. (수정되었습니다 .NET의 메모리 장벽 질문을 위해 업데이트 된 실행 가능한 LINQPAD 프로그램이 되려면).

// Run this as a LINQPad program in "Release Mode".
// ~ It will never terminate on .NET 4.5.2 / x64. ~
// The program will terminate in "Debug Mode" and may terminate
// in other CLR runtimes and architecture targets.
class X {
    // Adding {volatile} would 'fix the problem', as it prevents the JIT
    // optimization that results in the non-terminating code.
    public int terminate = 0;
    public int y;

    public void Run() {
        var r = new ManualResetEvent(false);
        var t = new Thread(() => {
            int x = 0;
            r.Set();
            // Using Volatile.Read or otherwise establishing
            // an Acquire Barrier would disable the 'bad' optimization.
            while(terminate == 0){x = x * 2;}
            y = x;
        });

        t.Start();
        r.WaitOne();
        Interlocked.Increment(ref terminate);
        t.Join();
        Console.WriteLine("Done: " + y);
    }
}

void Main()
{
    new X().Run();
}

설명 .NET의 메모리 장벽:

이번에는 하드웨어가 아니라 JIT입니다. JIT는 [EAX 레지스터에서] 변수 종료 값을 캐시했으며 이제 위에서 강조 표시된 루프에 갇혀 있습니다.

A를 사용합니다 lock 또는 추가 a Thread.MemoryBarrier while 루프 내부는 문제를 해결합니다. 또는 사용할 수도 있습니다 Volatile.Read 또는 a volatile 필드]. 여기서 메모리 장벽의 목적은 JIT 최적화를 억제하는 것입니다. 이제 우리는 방법을 보았습니다 소프트웨어 및 하드웨어는 메모리 작업을 재정렬 할 수 있습니다, 기억 장벽에 대해 논의 할 시간입니다 ..

즉, 추가입니다 장벽 읽기 측면에서 구성이 필요합니다 컴파일 및 JIT 재주문 / 최적화 문제를 방지합니다: 이것은 메모리 일관성과 다른 문제입니다!

첨가 volatile 여기있을 것입니다 예방하다 JIT 최적화는 그러한 결과가 발생하더라도 '문제를 해결'합니다. 이 프로그램은 Volatile.Read 또는 장벽을 유발하는 다양한 다른 작업 중 하나 : 이러한 장벽은 기본 하드웨어 메모리 울타리만큼 CLR/JIT 프로그램 정확성의 일부입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top