참조 할당은 원자 적이므로 Interlocked.Exchange (ref Object, Object)가 필요한 이유는 무엇입니까?


108

내 다중 스레드 asmx 웹 서비스에는 몇 개로 구성 List<T>되고으로 Dictionary<T>표시된 SystemData 유형의 클래스 필드 _allData가 volatile있습니다. 시스템 데이터 ( _allData)는 가끔 새로 고침되며라는 다른 객체를 만들고 newData새 데이터로 데이터 구조를 채 웁니다. 완료되면 할당합니다.

private static volatile SystemData _allData

public static bool LoadAllSystemData()
{
    SystemData newData = new SystemData();
    /* fill newData with up-to-date data*/
     ...
    _allData = newData.
} 

이것은 할당이 원자적이고 이전 데이터에 대한 참조가있는 스레드가 계속 사용하고 나머지는 할당 직후 새 시스템 데이터를 가지고 있기 때문에 작동합니다. 그러나 제 동료는 일부 플랫폼에서 참조 할당이 원자 적이라는 보장이 없기 때문에 volatile키워드와 간단한 할당 을 사용하는 대신 사용해야 InterLocked.Exchange한다고 말했습니다. 또한 : 때 선언 the _allData으로 필드를volatile

Interlocked.Exchange<SystemData>(ref _allData, newData); 

"휘발성 필드에 대한 참조는 휘발성으로 처리되지 않습니다"라는 경고를 생성합니다. 이에 대해 어떻게 생각해야합니까?

답변:


179

여기에 많은 질문이 있습니다. 한 번에 하나씩 고려 :

참조 할당은 원자 적이므로 Interlocked.Exchange (ref Object, Object)가 필요한 이유는 무엇입니까?

참조 할당은 원자 적입니다. Interlocked.Exchange는 할당을 참조 만하는 것이 아닙니다. 변수의 현재 값을 읽고 이전 값을 숨기고 새 값을 변수에 할당합니다.이 모든 것이 원자 적 연산입니다.

제 동료는 일부 플랫폼에서 참조 할당이 원자 적이라는 것이 보장되지 않는다고 말했습니다. 제 동료가 맞습니까?

아니요. 참조 할당은 모든 .NET 플랫폼에서 원 자성이 보장됩니다.

제 동료는 거짓 전제에서 추론하고 있습니다. 그것은 그들의 결론이 틀렸다는 것을 의미합니까?

반드시 그런 것은 아닙니다. 동료가 나쁜 이유로 좋은 조언을 줄 수 있습니다. Interlocked.Exchange를 사용해야하는 다른 이유가있을 수 있습니다. 잠금없는 프로그래밍은 엄청나게 어렵고 현장 전문가가지지하는 잘 확립 된 관행에서 벗어나는 순간 잡초에 빠져 최악의 경쟁 조건에 처하게됩니다. 저는이 분야의 전문가도 아니고 귀하의 코드에 대한 전문가도 아니므로 어떤 식 으로든 판단을 내릴 수 없습니다.

"휘발성 필드에 대한 참조는 휘발성으로 처리되지 않습니다"라는 경고를 생성합니다. 이에 대해 어떻게 생각해야합니까?

이것이 일반적인 문제인 이유를 이해해야합니다. 그러면이 특정 경우에 경고가 중요하지 않은 이유를 이해할 수 있습니다.

컴파일러가이 경고를 표시하는 이유는 필드를 휘발성으로 표시한다는 것은 "이 필드가 여러 스레드에서 업데이트 될 것입니다.이 필드의 값을 캐시하는 코드를 생성하지 말고 모든 읽기 또는 쓰기를 확인하십시오. 이 필드는 프로세서 캐시 불일치를 통해 "시간상 앞뒤로 이동"되지 않습니다.

(이미 모든 것을 이해하고 있다고 가정합니다. 휘발성의 의미와 프로세서 캐시 시맨틱에 미치는 영향에 대해 자세히 이해하지 못한 경우 작동 방식을 이해하지 못하고 휘발성을 사용해서는 안됩니다. 잠금없는 프로그램 프로그램이 올바른지 확인하기가 매우 어렵습니다. 프로그램이 작동하는 방식을 이해하고 있기 때문에 우연히 옳지 않은지 확인하십시오.)

이제 해당 필드에 ref를 전달하여 휘발성 필드의 별칭 인 변수를 만든다고 가정합니다. 호출 된 메서드 내에서 컴파일러는 참조가 휘발성 의미를 가져야한다는 것을 알 이유가 전혀 없습니다! 컴파일러는 휘발성 필드에 대한 규칙을 구현하지 못하는 메서드에 대한 코드를 쾌활하게 생성하지만 변수 휘발성 필드입니다. 그것은 잠금없는 논리를 완전히 망칠 수 있습니다. 휘발성 필드는 항상 휘발성 의미론으로 액세스 된다는 가정이 항상 있습니다. 때로는 그것을 휘발성으로 취급하고 다른 시간에는 취급하지 않습니다. 항상 일관성 이 있어야합니다. 그렇지 않으면 다른 액세스에 대한 일관성을 보장 할 수 없습니다.

따라서 컴파일러는 신중하게 개발 한 잠금없는 논리를 완전히 엉망으로 만들 것이기 ​​때문에이 작업을 수행 할 때 경고를 표시합니다.

물론 Interlocked.Exchange 휘발성 필드를 예상하고 올바른 작업을 수행하도록 작성되었습니다. 따라서 경고는 오해의 소지가 있습니다. 나는 이것을 매우 후회한다. 우리가해야 할 일은 Interlocked.Exchange와 같은 메서드의 작성자가 "참조를 취하는이 메서드는 변수에 휘발성 의미를 적용하므로 경고를 억제"라는 속성을 메서드에 넣을 수있는 메커니즘을 구현하는 것입니다. 아마도 향후 버전의 컴파일러에서 그렇게 할 것입니다.


1
Interlocked.Exchange는 또한 메모리 장벽이 생성된다는 것을 보장합니다. 예를 들어 새 개체를 만든 다음 몇 가지 속성을 할당 한 다음 Interlocked.Exchange를 사용하지 않고 다른 참조에 개체를 저장하면 컴파일러가 해당 작업의 순서를 엉망으로 만들 수 있으므로 스레드가 아닌 두 번째 참조에 액세스 할 수 있습니다. 안전한. 정말 그래요? Interlocked.Exchange를 사용하는 것이 이치에 맞습니까?
Mike

12
@Mike : 저 잠금 멀티 스레드 상황에서 관찰 될 수있는 것에 관해서는 다음 사람만큼 무지합니다. 대답은 아마도 프로세서마다 다를 것입니다. 전문가에게 질문을하거나 관심이 있다면 주제에 대해 읽어야합니다. Joe Duffy의 책과 그의 블로그는 시작하기에 좋은 곳입니다. 내 규칙 : 멀티 스레딩을 사용하지 마십시오. 필요한 경우 변경 불가능한 데이터 구조를 사용하십시오. 할 수 없다면 자물쇠를 사용하십시오. 잠금없이 변경 가능한 데이터가 있어야하는 경우에만 낮은 잠금 기술을 고려해야합니다.
Eric Lippert

Eric에게 답변 해 주셔서 감사합니다. 정말 흥미 롭습니다. 그래서 멀티 스레딩 및 잠금 전략에 대한 책과 블로그를 읽고 코드에서이를 구현하려고합니다. 그러나 배울 수 많은 ... 거기에 여전히
마이크

2
@EricLippert "멀티 스레딩을 사용하지 마십시오"와 "필요한 경우 변경 불가능한 데이터 구조를 사용하십시오"사이에 "하위 스레드가 독점적으로 소유 한 입력 개체 만 사용하고 부모 스레드가 결과를 사용하도록하는 중급 및 매우 일반적인 수준"을 삽입합니다. 아이가 끝났을 때만 ". 에서와 같이 var myresult = await Task.Factory.CreateNew(() => MyWork(exclusivelyLocalStuffOrValueTypeOrCopy));.
John

1
@John : 좋은 생각입니다. 나는 쓰레드를 값싼 프로세스처럼 다루려고 노력한다. 그들은 메인 프로그램의 데이터 구조 내에서 두 번째 제어 쓰레드가 아닌 작업을 수행하고 결과를 생성하기 위해 존재한다. 그러나 스레드가 수행하는 작업량이 너무 커서 프로세스처럼 처리하는 것이 합리적이라면 프로세스로 처리한다고 말합니다!
Eric Lippert 2014

9

동료가 착각했거나 C # 언어 사양에없는 것을 알고 있습니다.

5.5 변수 참조의 원 자성 :

"다음 데이터 유형의 읽기 및 쓰기는 원자 적입니다 : bool, char, byte, sbyte, short, ushort, uint, int, float 및 reference 유형."

따라서 손상된 값을 얻을 위험없이 휘발성 참조에 쓸 수 있습니다.

물론 한 번에 둘 이상의 스레드가 수행하는 위험을 최소화하기 위해 새 데이터를 가져올 스레드를 결정하는 방법에주의해야합니다.


3
@guffa : 네 저도 읽었습니다. 이렇게하면 "참조 할당이 원자 적이므로 Interlocked.Exchange (ref Object, Object)가 필요한 이유는 무엇입니까?"라는 질문이 남습니다. unaswered
char m

@zebrabox : 무슨 뜻이야? 그렇지 않을 때? 당신은 무엇을 하시겠습니까?
char m

@matti : 원자 적 연산으로 값을 읽고 써야 할 때 필요합니다.
Guffa 2010

.NET에서 메모리가 제대로 정렬되지 않는 것에 대해 얼마나 자주 걱정해야합니까? Interop 무거운 물건?
Skurmedel 2010

1
@zebrabox : 사양에는 그 경고가 나열되어 있지 않으며 매우 명확한 진술을 제공합니다. 참조 읽기 또는 쓰기가 원 자성이되지 않는 비 메모리 정렬 상황에 대한 참조가 있습니까? 사양의 매우 명확한 언어를 위반하는 것 같습니다.
TJ Crowder 2012

6

연동. 교환 <T>

원자 단위 연산으로 지정된 유형 T의 변수를 지정된 값으로 설정하고 원래 값을 반환합니다.

그것은 변경하고 원래의 값을 반환합니다. 그것은 당신이 그것을 바꾸고 싶기 때문에 쓸모가 없습니다. 그리고 Guffa가 말했듯이 그것은 이미 원자 적입니다.

프로파일 러가 애플리케이션의 병목 현상으로 입증되지 않는 한 잠금 해제를 고려해야합니다. 코드가 옳다는 것을 이해하고 증명하는 것이 더 쉽습니다.


3

Iterlocked.Exchange() 원 자성뿐만 아니라 메모리 가시성도 처리합니다.

다음 동기화 기능은 적절한 장벽을 사용하여 메모리 순서를 보장합니다.

중요한 섹션에 들어가거나 나가는 기능

동기화 객체를 신호하는 기능

대기 기능

연동 기능

동기화 및 다중 프로세서 문제

이는 원 자성 외에도 다음을 보장한다는 것을 의미합니다.

  • 그것을 호출하는 스레드의 경우 :
    • (컴파일러, 런타임 또는 하드웨어에 의해) 명령어의 재정렬이 수행되지 않습니다.
  • 모든 스레드 :
    • 이 명령어 이전에 발생한 메모리 읽기는이 명령어가 변경 한 내용을 볼 수 없습니다.
    • 이 명령어 이후의 모든 읽기에는이 명령어로 변경된 내용이 표시됩니다.
    • 이 명령어 이후의 메모리에 대한 모든 쓰기는이 명령어 변경이 메인 메모리에 도달 한 후에 발생합니다 (이 명령어 변경이 완료되면 메인 메모리로 플러시하고 하드웨어가 자신의 온 타이밍을 플러시하지 않도록 함).
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.