[c++] i ++가 스레드로부터 안전하지 않다고 들었습니다. ++ i는 스레드로부터 안전합니까?

나는 어셈블리에서 원래 값을 임시 어딘가에 저장하고 증가시킨 다음 컨텍스트 스위치에 의해 중단 될 수있는 대체하기 때문에 i ++가 스레드로부터 안전한 진술이 아니라고 들었습니다.

그러나 ++ i에 대해 궁금합니다. 내가 알 수있는 한, 이것은 ‘add r1, r1, 1’과 같은 단일 어셈블리 명령어로 축소되며 하나의 명령어 일 뿐이므로 컨텍스트 스위치에 의해 중단되지 않습니다.

누구든지 명확히 할 수 있습니까? x86 플랫폼이 사용되고 있다고 가정합니다.



답변

당신은 잘못 들었습니다. 그것은 잘가있을 수 있습니다 "i++"특정 컴파일러와 특정 프로세서 아키텍처에 대한 스레드 안전하지만 모두의 기준에 위임 아니에요. 사실, 멀티 스레딩은 ISO C 또는 C ++ 표준 (a)의 일부가 아니기 때문에 컴파일 될 것이라고 생각하는 것에 따라 스레드로부터 안전한 것으로 간주 할 수 없습니다.

다음 ++i과 같은 임의의 시퀀스로 컴파일 할 수 있습니다.

load r0,[i]  ; load memory into reg 0
incr r0      ; increment reg 0
stor [i],r0  ; store reg 0 back to memory

메모리 증가 명령이없는 내 (가상) CPU에서는 스레드로부터 안전하지 않습니다. 또는 똑똑하고 다음과 같이 컴파일 할 수 있습니다.

lock         ; disable task switching (interrupts)
load r0,[i]  ; load memory into reg 0
incr r0      ; increment reg 0
stor [i],r0  ; store reg 0 back to memory
unlock       ; enable task switching (interrupts)

여기서 인터럽트를 lock비활성화하고 unlock활성화 합니다 . 그러나 그럼에도 불구하고 메모리를 공유하는 이러한 CPU 중 두 개 이상이있는 아키텍처에서는 스레드로부터 안전하지 않을 수 있습니다 ( lock한 CPU에 대한 인터럽트 만 비활성화 할 수 있음).

언어 자체 (또는 해당 언어에 내장되지 않은 경우 라이브러리)는 스레드로부터 안전한 구조를 제공하므로 생성되는 기계 코드에 대한 이해 (또는 오해 가능성)에 의존하지 말고이를 사용해야합니다.

Java synchronizedpthread_mutex_lock()(일부 운영 체제에서 C / C ++에서 사용 가능) 와 같은 것들은 (a) .


(a) 이 질문은 C11 및 C ++ 11 표준이 완성되기 전에 질문되었습니다. 이러한 반복은 이제 원자 데이터 유형을 포함하여 언어 사양에 스레딩 지원을 도입했습니다 (하지만 일반적으로 스레드와 스레드 는 적어도 C에서는 선택 사항입니다 ).


답변

++ i 또는 i ++에 대해 포괄적 인 진술을 할 수 없습니다. 왜? 32 비트 시스템에서 64 비트 정수를 증가시키는 것을 고려하십시오. 기본 머신에 “로드, 증가, 저장”명령이 쿼드 단어 인 경우를 제외하고, 해당 값을 증가 시키려면 여러 명령이 필요합니다.이 명령 중 하나는 스레드 컨텍스트 스위치에 의해 중단 될 수 있습니다.

또한 ++i항상 “가치에 하나를 추가”하는 것은 아닙니다. C와 같은 언어에서 포인터를 증가 시키면 실제로 가리키는 물체의 크기가 추가됩니다. 즉, i32 바이트 구조에 대한 포인터 인 경우 ++i32 바이트를 추가합니다. 거의 모든 플랫폼이 원자적인 “메모리 주소에서 값 증가”명령을 가지고있는 반면, 모든 플랫폼이 원자 “메모리 주소에서 값에 임의의 값 추가”명령을 가지고있는 것은 아닙니다.


답변

둘 다 스레드에 안전하지 않습니다.

CPU는 메모리로 직접 수학을 할 수 없습니다. 메모리에서 값을로드하고 CPU 레지스터로 수학을 수행하여 간접적으로 수행합니다.

i ++

register int a1, a2;

a1 = *(&i) ; // One cpu instruction: LOAD from memory location identified by i;
a2 = a1;
a1 += 1;
*(&i) = a1;
return a2; // 4 cpu instructions

++ i

register int a1;

a1 = *(&i) ;
a1 += 1;
*(&i) = a1;
return a1; // 3 cpu instructions

두 경우 모두 예측할 수없는 i 값을 발생시키는 경쟁 조건이 있습니다.

예를 들어, 각각 레지스터 a1, b1을 사용하는 두 개의 동시 ++ i 스레드가 있다고 가정합니다. 그리고 컨텍스트 전환은 다음과 같이 실행됩니다.

register int a1, b1;

a1 = *(&i);
a1 += 1;
b1 = *(&i);
b1 += 1;
*(&i) = a1;
*(&i) = b1;

결과적으로 나는 i + 2가되지 않고 i + 1이되는데 이는 잘못된 것입니다.

이를 해결하기 위해 모드 CPU는 컨텍스트 전환이 비활성화되는 간격 동안 일종의 LOCK, UNLOCK cpu 명령을 제공합니다.

Win32에서는 InterlockedIncrement ()를 사용하여 스레드 안전성을 위해 i ++를 수행합니다. 뮤텍스에 의존하는 것보다 훨씬 빠릅니다.


답변

멀티 코어 환경에서 스레드간에 int를 공유하는 경우 적절한 메모리 장벽이 필요합니다. 이는 연동 된 명령어 (예 : win32의 InterlockedIncrement 참조)를 사용하거나 특정 스레드 안전을 보장하는 언어 (또는 컴파일러)를 사용하는 것을 의미 할 수 있습니다. CPU 수준의 명령 재정렬 및 ​​캐시 및 기타 문제를 통해 보장되지 않는 한 스레드간에 공유되는 항목이 안전하다고 가정하지 마십시오.

편집 : 대부분의 아키텍처에서 가정 할 수있는 한 가지는 적절하게 정렬 된 단일 단어를 처리하는 경우 함께 으 깨진 두 값의 조합을 포함하는 단일 단어로 끝나지 않는다는 것입니다. 두 번의 쓰기가 서로 겹쳐서 발생하면 하나는 이기고 다른 하나는 버려집니다. 주의를 기울이면이를 활용하여 ++ i 또는 i ++가 단일 작성기 / 다중 판독기 상황에서 스레드로부터 안전하다는 것을 확인할 수 있습니다.


답변

C ++에서 원자 적 증가를 원하면 C ++ 0x 라이브러리 ( std::atomic데이터 유형) 또는 TBB와 같은 것을 사용할 수 있습니다 .

GNU 코딩 가이드 라인에서 한 단어에 맞는 데이터 유형을 업데이트하는 것이 “보통 안전”하다고 말한 적이 있었지만 SMP 시스템에는 잘못 되었고 일부 아키텍처에서는 잘못되었으며 최적화 컴파일러를 사용할 때는 잘못되었습니다.


“한 단어로 된 데이터 유형 업데이트”주석을 명확히하려면 :

SMP 시스템의 두 CPU가 동일한주기에서 동일한 메모리 위치에 쓴 다음 변경 사항을 다른 CPU와 캐시에 전파 할 수 있습니다. 한 단어의 데이터 만 기록되어 쓰기가 완료되는 데 한 주기만 걸리더라도 동시에 발생하므로 어떤 쓰기가 성공했는지 보장 할 수 없습니다. 부분적으로 업데이트 된 데이터는 얻지 못하지만이 경우를 처리 할 다른 방법이 없기 때문에 하나의 쓰기가 사라집니다.

비교 및 교체는 여러 CPU간에 적절하게 조정되지만 한 단어 데이터 유형의 모든 변수 할당이 비교 및 ​​교체를 사용할 것이라고 믿을 이유가 없습니다.

그리고 최적화 컴파일러는 영향을 미치지 않지만 로드 / 저장이 컴파일되는 방식 에 로드 / 저장이 발생할 변경 수 있으므로 읽기 및 쓰기가 소스 코드에 나타나는 것과 동일한 순서로 발생할 것으로 예상되는 경우 심각한 문제를 일으킬 수 있습니다 ( 가장 유명한 이중 검사 잠금은 바닐라 C ++에서 작동하지 않습니다.)

참고 나의 원래 대답은 또한 Intel 64 비트 아키텍처가 64 비트 데이터를 처리 할 때 손상되었다고 말했습니다. 그것은 사실이 아니기 때문에 답을 편집했지만 내 편집은 PowerPC 칩이 고장 났다고 주장했습니다. 즉 치값 (즉, 상수)을 레지스터로 읽을 때도 마찬가지입니다 (목록 2 및 목록 4 아래의 “포인터로드”라는 두 섹션 참조). 그러나 한 사이클 ( lmw) 에서 메모리에서 데이터를로드하는 지침이 있으므로 내 대답의 해당 부분을 제거했습니다.


답변

C / C ++의 x86 / Windows에서는 스레드로부터 안전하다고 가정해서는 안됩니다. 당신은 사용해야 InterlockedIncrement ()InterlockedDecrement ()를 사용하면 원자 작업을 필요로합니다.


답변

프로그래밍 언어가 스레드에 대해 아무 말도하지 않지만 다중 스레드 플랫폼에서 실행되는 경우 어떻게 모든 언어 구조가 스레드로부터 안전 할 수 있습니까?

다른 사람들이 지적했듯이 플랫폼 별 호출을 통해 변수에 대한 다중 스레드 액세스를 보호해야합니다.

플랫폼 특이성을 추상화하는 라이브러리가 있으며 다가오는 C ++ 표준은 스레드에 대처하기 위해 메모리 모델을 조정했습니다 (따라서 스레드 안전성을 보장 할 수 있음).