문제

오늘 나는이 질문을 발견했다.

코드가 있습니다

static int counter = 0;
void worker() {
    for (int i = 1; i <= 10; i++)
        counter++;
}

만약에 worker 두 개의 다른 스레드에서 호출 될 것입니다. counter 둘 다 끝난 후에?

나는 그것이 실제로 무엇이든 될 수 있다는 것을 알고 있습니다. 그러나 내 내면의 내장은 나에게 그 말을한다 counter++ 아마도 단일 어셈블러 명령으로 변환 될 가능성이 높으며 두 스레드가 동일한 코어에서 실행되면 counter 20이 될 것입니다.

그러나 해당 스레드가 다른 코어 나 프로세서에서 실행되면 마이크로 코드에 레이스 조건이있을 수 있습니까? 하나의 어셈블러 명령이 항상 원자 연산으로 볼 수 있습니까?

도움이 되었습니까?

해결책

특히 X86 및 예제와 관련하여 다음과 같습니다. counter++, 편집 할 수있는 방법에는 여러 가지가 있습니다. 가장 사소한 예는 다음과 같습니다.

inc counter

이것은 다음 마이크로 작업으로 해석됩니다.

  • counter CPU의 숨겨진 레지스터에
  • 레지스터를 증가시킵니다
  • 업데이트 된 레지스터를 저장하십시오 counter

이것은 본질적으로 다음과 같습니다.

mov eax, counter
inc eax
mov counter, eax

다른 에이전트가 업데이트되는 경우에 유의하십시오 counter 부하와 상점 사이에 반영되지 않습니다. counter 가게 후. 이 에이전트는 동일한 코어의 다른 스레드, 동일한 CPU의 다른 코어, 동일한 시스템의 다른 CPU 또는 DMA (직접 메모리 액세스)를 사용하는 일부 외부 에이전트 일 수 있습니다.

당신이 이것을 보장하고 싶다면 inc 원자력입니다 lock 접두사:

lock inc counter

lock 아무도 업데이트 할 수 없다고 보장합니다 counter 부하와 상점 사이.


더 복잡한 지침과 관련하여, 당신은 일반적으로 그들이 지원하지 않는 한 원자 적으로 실행할 것이라고 가정 할 수 없습니다. lock 접두사.

다른 팁

대답은 다음과 같습니다.

다음은 어셈블러 지시가 무엇인지에 대한 혼란이 있습니다. 일반적으로 하나의 어셈블러 명령은 정확히 하나의 기계 명령어로 변환됩니다. 실현은 당신이 매크로를 사용할 때입니다. 그러나 당신은 그것을 알고 있어야합니다.

즉, 질문이 줄어든다.

좋은 옛날에는 그랬습니다. 그러나 오늘날 복잡한 CPU, 오랜 지침, 하이퍼 스레딩으로 ... 그렇지 않습니다. 일부 CPU는이를 보장합니다 약간 증분/감소 지침은 원자입니다. 그 이유는 매우 간단한 동시 화를 위해 깔끔하기 때문입니다.

또한 일부 CPU 명령은 그렇게 문제가되지 않습니다. 간단한 페치 (프로세서가 한 조각으로 가져올 수있는 한 조각의 데이터)가 있으면 전혀 나눌 수있는 것이 없기 때문에 페치 자체는 원자가입니다. 그러나 정렬되지 않은 데이터가 있으면 다시 복잡해집니다.

대답은 다음과 같습니다. 공급 업체의 기계 명령 매뉴얼을주의 깊게 읽으십시오. 의심의 여지없이, 그렇지 않습니다!

편집 : 아, 지금 봤어요. ++ 카운터도 요청합니다. "번역 될 가능성이 가장 높은"진술은 전혀 신뢰할 수 없습니다. 이것은 크게 컴파일러에 따라 다릅니다! 컴파일러가 다른 최적화를 할 때 더 어려워집니다.

항상 그런 것은 아닙니다 - 일부 아키텍처에서는 하나의 어셈블리 명령어가 하나의 기계 코드 명령으로 변환되고 다른 조립품 명령어는 하나의 기계 코드 명령으로 변환되지만 다른 조립품은 그렇지 않습니다.

게다가 - 할 수 있습니다 절대 사용중인 프로그램 언어가 겉보기에 간단한 코드 라인을 하나의 어셈블리 명령어로 컴파일한다고 가정합니다. 또한 일부 아키텍처에서는 하나의 기계 코드가 원자 적으로 실행된다고 가정 할 수 없습니다.

코딩하는 언어에 따라 대신 적절한 동기화 기술을 사용하십시오.

  1. 하이퍼 스레딩 기술이없는 단일 32 비트 프로세서에서 32 비트 이하의 정수 변수에 대한 증분/감소 작업은 원자입니다.
  2. 하이퍼 스레딩 기술이있는 프로세서 또는 다중 프로세서 시스템에서 증분/감소 작업은 원자력으로 실행되지 않습니다.

Nathan의 의견에 의해 무효화 :인텔 X86 어셈블러를 올바르게 기억한다면 Inc 명령은 레지스터에만 작동하며 메모리 위치에 직접 작동하지 않습니다.

따라서 Coun 최소한 세 가지 지침이 있습니다 :로드 카운터 변수 등록, 등록 증분, 카운터로 다시로드 등록하십시오. 그리고 그것은 x86 아키텍처를위한 것입니다.

요컨대, 언어 사양에 의해 지정되고 사용중인 컴파일러가 사양을 지원하지 않는 한 원자력에 의존하지 마십시오.

아니요 이것을 가정 할 수 없습니다. 컴파일러 사양에 명확하게 언급되지 않는 한. 또한 아무도 하나의 단일 어셈블러 명령이 실제로 원자를 보장 할 수 없습니다. 실제로 각 어셈블러 명령어는 마이크로 코드 작업 -UOPS로 변환됩니다.
또한 인종 조건의 문제는 메모리 모델 (일관성, 순차적, 릴리스 일관성 등)과 밀접하게 결합되어 있으며, 각각에 대한 답과 결과는 다를 수 있습니다.

또 다른 문제는 변수를 휘발성으로 선언하지 않으면 생성 된 코드가 모든 루프 반복에서 메모리를 업데이트하지 않을 것이며, 루프 끝에서만 메모리가 업데이트된다는 것입니다.

귀하의 질문에 대한 실제 답이 아닐 수도 있지만 원하는 경우 (C#또는 다른 .NET 언어라고 가정) counter++ 실제로 멀티 스레드 원자가 되려면 사용할 수 있습니다 System.Threading.Interlocked.Increment(counter).

왜/어떻게 다양한 방법에 대한 실제 정보에 대한 다른 답변을 확인하십시오. counter++ 원자가 될 수 없습니다. ;-)

대부분의 경우에, 아니요. 실제로 x86에서는 지침을 수행 할 수 있습니다.

push [address]

C에서는 다음과 같습니다.

*stack-- = *address;

이것은 수행됩니다 두 개의 메모리가 하나의 명령어로 전송됩니다.

기본적으로 1 클럭 사이클에서 수행하는 것은 불가능합니다. 하나 한 주기로 메모리 전송이 불가능합니다!

다른 많은 프로세서에서 메모리 시스템과 프로세서 사이의 분리가 더 큽니다. (종종 이러한 프로세서는 ARM 및 PowerPC와 같은 메모리 시스템에 따라 거의 또는 큰 엔디안이 될 수 있습니다.) 이는 메모리 시스템이 읽기 및 쓸 수있는 경우 원자 동작에 영향을 미칩니다.

이를 위해 메모리 장벽이 있습니다 (http://en.wikipedia.org/wiki/memory_barrier)

간단히 말해서, 원자 지침은 인텔에서 충분하지만 (관련 잠금 접두사 포함) 메모리 I/O가 동일한 순서가 아닐 수 있으므로 인텔이 아닌 경우 더 많은 작업을 수행해야합니다.

이것은 "잠금"솔루션을 인텔에서 다른 아키텍처로 포팅 할 때 알려진 문제입니다.

(X86의 멀티 프로세서 (멀티 코어 아님) 시스템은 최소한 64 비트 모드에서 메모리 장벽이 필요한 것으로 보입니다.

액세스에 대한 레이스 조건을 얻을 수 있다고 생각합니다.

증분 카운터에서 원자력 작동을 보장하려면 ++ 카운터를 사용해야합니다.

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