나는 어셈블리에서 원래 값을 임시 어딘가에 저장하고 증가시킨 다음 컨텍스트 스위치에 의해 중단 될 수있는 대체하기 때문에 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 synchronized
및 pthread_mutex_lock()
(일부 운영 체제에서 C / C ++에서 사용 가능) 와 같은 것들은 (a) .
(a) 이 질문은 C11 및 C ++ 11 표준이 완성되기 전에 질문되었습니다. 이러한 반복은 이제 원자 데이터 유형을 포함하여 언어 사양에 스레딩 지원을 도입했습니다 (하지만 일반적으로 스레드와 스레드 는 적어도 C에서는 선택 사항입니다 ).
답변
++ i 또는 i ++에 대해 포괄적 인 진술을 할 수 없습니다. 왜? 32 비트 시스템에서 64 비트 정수를 증가시키는 것을 고려하십시오. 기본 머신에 “로드, 증가, 저장”명령이 쿼드 단어 인 경우를 제외하고, 해당 값을 증가 시키려면 여러 명령이 필요합니다.이 명령 중 하나는 스레드 컨텍스트 스위치에 의해 중단 될 수 있습니다.
또한 ++i
항상 “가치에 하나를 추가”하는 것은 아닙니다. C와 같은 언어에서 포인터를 증가 시키면 실제로 가리키는 물체의 크기가 추가됩니다. 즉, i
32 바이트 구조에 대한 포인터 인 경우 ++i
32 바이트를 추가합니다. 거의 모든 플랫폼이 원자적인 “메모리 주소에서 값 증가”명령을 가지고있는 반면, 모든 플랫폼이 원자 “메모리 주소에서 값에 임의의 값 추가”명령을 가지고있는 것은 아닙니다.
답변
둘 다 스레드에 안전하지 않습니다.
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 ++ 표준은 스레드에 대처하기 위해 메모리 모델을 조정했습니다 (따라서 스레드 안전성을 보장 할 수 있음).