문제

수업에 a가 있다고 가정 해 봅시다 public int counter 여러 스레드에 의해 액세스되는 필드. 이것 int 단지 증가하거나 감소됩니다.

이 필드를 증가시키기 위해 어떤 접근법을 사용해야하고, 그 이유는 무엇입니까?

  • lock(this.locker) this.counter++;,
  • Interlocked.Increment(ref this.counter);,
  • 액세스 수정자를 변경하십시오 counter 에게 public volatile.

이제 내가 발견했습니다 volatile, 나는 많은 것을 제거하고 있습니다 lock 진술과 사용 Interlocked. 그러나 이것을하지 않는 이유가 있습니까?

도움이 되었습니까?

해결책

최악 (실제로 작동하지 않음)

액세스 수정자를 변경하십시오 counter 에게 public volatile

다른 사람들이 언급했듯이, 이것은 실제로 전혀 안전하지 않습니다. 요점 volatile 여러 CPU에서 실행되는 여러 스레드가 데이터를 캐시하고 지침을 재정렬 할 수 있다는 것입니다.

그렇다면 ~ 아니다 volatile, 및 CPU A는 값을 증가 시키면 CPU B는 실제로 얼마 후에 증가 된 값을 보지 못하므로 문제가 발생할 수 있습니다.

그렇다면 volatile, 이것은 두 CPU가 동시에 동일한 데이터를 볼 수 있도록합니다. 그것은 당신이 피하려고하는 문제인 그들의 읽기를 인터리브하고 작문을 쓰는 것을 전혀 멈추지 않습니다.

두번째로 좋은:

lock(this.locker) this.counter++;

이것은 안전합니다 (기억하면 lock 당신이 접근하는 다른 곳 this.counter). 다른 스레드가 보호되는 다른 코드를 실행하는 것을 방지합니다. locker. 잠금 장치를 사용하면 위와 같이 멀티 CPU 재정의 문제를 방지합니다.

문제는 잠금이 느리고 재사용하면 locker 실제로 관련이없는 다른 곳에서는 아무 이유없이 다른 스레드를 차단할 수 있습니다.

최고

Interlocked.Increment(ref this.counter);

중단 할 수없는 'One Hit'으로 읽기, 증분 및 쓰기를 효과적으로 수행하므로 안전합니다. 이로 인해 다른 코드에는 영향을 미치지 않으며 다른 곳에서도 잠그는 것을 기억할 필요가 없습니다. 또한 MSDN이 말한 것처럼 현대 CPU에서는 종종 말 그대로 단일 CPU 명령어입니다).

그러나 다른 CPU를 주변에 가져 오는 것 또는 휘발성과 증가와 함께 결합 해야하는지 완전히 확신하지 못합니다.

인터록드 노트 :

  1. 연동 방법은 수많은 코어 또는 CPU에서 동시에 안전합니다.
  2. 인터 로크 방법은 실행하는 지침 주위에 전체 울타리를 적용하므로 재정렬이 발생하지 않습니다.
  3. 연동 방법 휘발성 필드에 대한 액세스를 필요로하거나 지원하지 마십시오., 휘발성이 주어진 필드의 작업 주위에 반 울타리를 배치하고 인터 로크가 전체 울타리를 사용하고 있습니다.

각주 : 실제로 휘발성이 실제로 좋은 것.

처럼 volatile 이러한 종류의 멀티 스레딩 문제를 방지하지는 않습니다. 좋은 예는 두 개의 스레드가 있다고 말하는 것입니다. 하나는 항상 변수에 씁니다 ( queueLength), 그리고 항상 같은 변수에서 읽는 것.

만약에 queueLength 휘발성이없고, 스레드 A는 5 번 쓰기 할 수 있지만, 스레드 B는 그 글이 지연되는 것으로 볼 수 있습니다 (또는 잠재적으로 잘못된 순서로).

해결책은 잠그는 것이지만이 상황에서는 휘발성을 사용할 수도 있습니다. 이를 통해 스레드 B는 항상 A가 작성한 가장 최신의 사항을 볼 수 있습니다. 그러나이 논리가 있음을 주목하십시오 읽지 않은 작가가 있고 글을 쓰지 않는 독자가 있다면 작동합니다. 그리고 당신이 쓰고있는 것이 원자 가치라면. 단일 읽기 모듈식이 낭비를하자마자 연동 조작으로 이동하거나 잠금 장치를 사용해야합니다.

다른 팁

편집하다: 의견에 언급했듯이 요즘 나는 기꺼이 사용합니다. Interlocked a 단일 변수 어디에 확실히 괜찮아. 더 복잡해지면 여전히 잠금으로 되돌릴 것입니다 ...

사용 volatile 읽기와 쓰기가 별도의 지침이기 때문에 증가해야 할 때 도움이되지 않습니다. 다른 스레드는 읽은 후에는 값을 바꿀 수 있지만 다시 쓰기 전에 값을 변경할 수 있습니다.

개인적으로 나는 거의 항상 잠금 - 확실히 변동성 또는 인터 로크보다 적합합니다. 내가 염려하는 한, 잠금없는 멀티 스레딩은 실제 스레딩 전문가를위한 것이며, 그 중 하나는 아닙니다. Joe Duffy와 그의 팀이 내가 구축 한 것만 큼 많은 잠금없이 물건을 병렬화 할 멋진 도서관을 구축한다면, 그것은 훌륭합니다. 그것은 훌륭합니다. 간단하게 유지하십시오.

"volatile"교체하지 않습니다 Interlocked.Increment! 변수가 캐시되지 않았지만 직접 사용되는지 확인합니다.

변수를 증가시키기 위해서는 실제로 세 가지 작업이 필요합니다.

  1. 읽다
  2. 증가
  3. 쓰다

Interlocked.Increment 세 부분을 모두 단일 원자 작업으로 수행합니다.

잠금 또는 연동 증분은 당신이 찾고있는 것입니다.

휘발성은 분명히 당신이 후에 그 후에있는 것이 아닙니다 - 그것은 단순히 컴파일러가 현재 코드 경로가 컴파일러가 메모리에서 읽기를 최적화 할 수 있도록 허용하더라도 변수를 항상 변경하도록 지시합니다.

예를 들어

while (m_Var)
{ }

M_VAR이 다른 스레드에서 False로 설정되었지만 휘발성으로 선언되지 않은 경우, 컴파일러는 CPU 레지스터에 대해 확인하여 무한 루프로 만들 수 있습니다 (예 : EAX이기 때문에 EG EAX M_VAR가 처음부터 가져온 것) M_VAR의 메모리 위치에 또 다른 읽기를 발행하는 대신 (이것은 캐시 될 수 있습니다. 지시 사항을 언급 한 다른 사람들의 이전 게시물은 단순히 x86/x64 아키텍처를 이해하지 못한다는 것을 보여줍니다. 휘발성 ~ 아니다 '재주문을 방지'하는 이전 게시물에서 암시 한대로 읽기/쓰기 장벽을 발행합니다. 실제로 MESI 프로토콜 덕분에 실제 결과가 물리적 메모리로 은퇴했는지 또는 단순히 로컬 CPU 캐시에 존재하는지 여부에 관계없이 우리가 읽은 결과는 항상 CPU에 걸쳐 동일하다는 것을 보장합니다. 나는 이것의 세부 사항에 너무 멀리 들어 가지 않을 것이지만, 이것이 잘못되면 인텔/AMD가 프로세서 리콜을 발행 할 것임을 확신합니다! 이것은 또한 우리가 순서 부전 실행 등을 신경 쓰지 않아도된다는 것을 의미합니다. 결과는 항상 순서대로 은퇴 할 수 있도록 보장됩니다. 그렇지 않으면 우리는 채워집니다!

인터 로크가 증가한 상태에서 프로세서는 꺼내어 주소에서 값을 가져온 다음 전체 캐시 라인 (잠금 XADD)의 독점 소유권을 갖는 동안 다른 프로세서가 수정할 수 없는지 확인하는 동안 모든 것을 수정할 수 없습니다. 그 가치.

휘발성을 사용하면 여전히 단 1 개의 명령으로 끝납니다 (JIT가 효율적이라고 가정 할 경우) -INC DWORD PTR [M_VAR]. 그러나 프로세서 (CPUA)는 인터 로크 버전으로 수행 한 모든 작업을 수행하면서 캐시 라인의 독점 소유권을 요구하지 않습니다. 당신이 상상할 수 있듯이, 이것은 다른 프로세서가 CPUA가 읽은 후 M_VAR에 업데이트 된 값을 다시 작성할 수 있음을 의미합니다. 따라서 지금 값을 두 번 증가시키는 대신 한 번만 끝납니다.

이것이 문제를 해결하기를 바랍니다.

자세한 내용은 '멀티 스레드 앱에서 저항 기술의 영향 이해'를 참조하십시오. http://msdn.microsoft.com/en-au/magazine/cc163715.aspx

추신 :이 매우 늦은 답변을 유도 한 이유는 무엇입니까? 모든 답변은 설명에서 너무나 뻔뻔스럽게 틀렸다 (특히 답으로 표시된 것). 나는 이것을 읽는 다른 사람을 위해 그것을 정리해야했다. 어깨를 으

pps 대상이 x86/x64이고 IA64가 아닌 것으로 가정합니다 (다른 메모리 모델이 있습니다). Microsoft의 ECMA 사양은 가장 강력한 메모리 모델 대신 가장 약한 메모리 모델을 지정한다는 점에서 나사로 고정됩니다 (가장 강력한 메모리 모델에 대해 항상 지정하는 것이 좋습니다. 그렇지 않으면 x86/에서 24-7에서 실행되는 코드에 따라 일관되게됩니다. x64는 IA64에서 전혀 실행되지 않을 수 있습니다. 인텔은 IA64에 대해 유사하게 강력한 메모리 모델을 구현했지만 - Microsoft는 이것을 자체적으로 인정했습니다. http://blogs.msdn.com/b/cbrumme/archive/2003/05/17/51445.aspx.

인터 로크 함수는 잠기지 않습니다. 그것들은 원자이며, 이는 증가하는 동안 컨텍스트 스위치의 가능성없이 완료 할 수 있음을 의미합니다. 따라서 교착 상태 나 기다릴 가능성이 없습니다.

나는 당신이 항상 그것을 자물쇠와 증분보다 선호해야한다고 말합니다.

휘발성은 다른 스레드에서 다른 스레드로 읽을 필요가있는 경우 유용하며, Optimizer가 변수에서 작업을 재정렬하지 않도록하려면 (Optimizer가 알지 못하는 다른 스레드에서 발생하기 때문에). 그것은 당신이 어떻게 증가하는지에 대한 직교 선택입니다.

Lock-Free Code에 대한 자세한 내용을 읽고 싶다면 글을 쓰는 올바른 방법입니다.

http://www.ddj.com/hpc-high-performance-computing/210604448

잠금 (...)은 작동하지만 스레드를 차단할 수 있으며 다른 코드가 호환되지 않는 방식으로 동일한 잠금 장치를 사용하는 경우 교착 상태를 유발할 수 있습니다.

인터록드.* 현대 CPU가 원시적으로 지원하기 때문에 올바른 방법입니다.

그 자체로는 휘발성이 맞지 않습니다. 검색하고 수정 된 값을 다시 작성하려는 스레드는 여전히 다른 스레드와 동일하게 충돌 할 수 있습니다.

이론이 실제로 어떻게 작동하는지 확인하기 위해 약간의 테스트를 수행했습니다. kennethxu.blogspot.com/2009/05/interlocked-vs-monitor-performance.html. 내 테스트는 비교에 초점을 맞추었지만 증분 결과는 비슷합니다. 다중 CPU 환경에서 인터 로크가 더 빨리 필요하지 않습니다. 다음은 2 년 된 16 CPU 서버에서 증가한 테스트 결과입니다. 테스트는 또한 증가 후 안전한 읽기와 관련이 있으며, 이는 실제 세계에서 일반적입니다.

D:\>InterlockVsMonitor.exe 16
Using 16 threads:
          InterlockAtomic.RunIncrement         (ns):   8355 Average,   8302 Minimal,   8409 Maxmial
    MonitorVolatileAtomic.RunIncrement         (ns):   7077 Average,   6843 Minimal,   7243 Maxmial

D:\>InterlockVsMonitor.exe 4
Using 4 threads:
          InterlockAtomic.RunIncrement         (ns):   4319 Average,   4319 Minimal,   4321 Maxmial
    MonitorVolatileAtomic.RunIncrement         (ns):    933 Average,    802 Minimal,   1018 Maxmial

다른 답변에 언급하고 싶습니다. volatile, Interlocked, 그리고 lock:

휘발성 키워드는 이러한 유형의 필드에 적용 할 수 있습니다.:

  • 참조 유형.
  • 포인터 유형 (안전하지 않은 상황에서). 포인터 자체는 휘발성이 될 수 있지만, 객체는 할 수없는 객체입니다. 다시 말해, "포인터"를 "휘발성"으로 선언 할 수는 없습니다.
  • 다음과 같은 간단한 유형 sbyte, byte, short, ushort, int, uint, char, float, 그리고 bool.
  • 다음 기본 유형 중 하나 인 열거 유형 : byte, sbyte, short, Ushort, int, 또는 uint.
  • 참조 유형으로 알려진 일반 유형 매개 변수.
  • IntPtr 그리고 UIntPtr.

다른 유형, 포함 double 그리고 long, 이러한 유형의 필드를 읽고 쓰는 것은 원자력으로 보장 할 수 없기 때문에 "휘발성"으로 표시 할 수 없습니다. 이러한 유형의 필드에 대한 멀티 스레드 액세스를 보호하려면 Interlocked 클래스 멤버 또는 The를 사용하여 액세스를 보호합니다lock 성명.

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