2011-11-28 5 views
24

전역 변수를 사용하여 스레드 간 통신을 구현하고 있습니다. 위의 코드에서은 대입 연산자 '='원자입니까?

//global var 
volatile bool is_true = true; 

//thread 1 
void thread_1() 
{ 
    while(1){ 
     int rint = rand() % 10; 
     if(is_true) { 
      cout << "thread_1: "<< rint <<endl; //thread_1 prints some stuff 
      if(rint == 3) 
       is_true = false; //here, tells thread_2 to start printing stuff 
     } 
    } 
} 

//thread 2 
void thread_2() 
{ 
    while(1){ 
     int rint = rand() % 10; 
     if(! is_true) { //if is_true == false 
      cout << "thread_1: "<< rint <<endl; //thread_2 prints some stuff 
      if(rint == 7) //7 
       is_true = true; //here, tells thread_1 to start printing stuff 
     } 
    } 
} 

int main() 
{ 
    HANDLE t1 = CreateThread(0,0, thread_1, 0,0,0); 
    HANDLE t2 = CreateThread(0,0, thread_2, 0,0,0); 
    Sleep(9999999); 
    return 0; 
} 

질문, 나는 thread_1과 thread_2 사이에 인쇄 전환 할 글로벌 VAR volatile bool is_true를 사용합니다.

여기은 할당 작업을 스레드로부터 안전하게 사용할 수 있는지 궁금합니다. ?

+0

원자 교환 프리미티브를 사용하는 것을 선호하지만 문제가 발생하는 시나리오는 해결할 수 없습니다. –

+0

@KerrekSB,이 시나리오는 무엇입니까? 글쎄, 난 그냥 내 질문을 설명하기 위해 즉흥적으로 :) – Alcott

+0

글쎄, 나는 두 스레드가 중요한 섹션을 입력 할 수 있도록 충분히 부러 것이로드 및 저장소의 순서를 의미 ... 일반적으로 하나의 순서를 입증 할 수 있어야합니다 특정 코드가 올바르지 않은 이유를 보여줍니다. 그래도 나는 그것을 볼 수 없다. 나는 여전히 코드가 마음에 들지 않지만 왜 그런지 증명할 수는 없다. –

답변

54

이 코드는 올바르게 정렬 된 4 바이트 및 포인터 크기 값에 대해서만 원 자성을 보장하므로 Win32에서는 스레드로부터 안전하지 않을 수 있습니다. bool은 이러한 유형 중 하나 일 수는 없습니다. (이것은 일반적으로 1 바이트 타입이다.)이 실패 할 수있는 방법의 실제 예를 요구하는 사람들을 위해

:

bool는 1 바이트 타입이라고 가정한다. is_true 변수가 또 다른 bool 변수 (즉, other_bool이라고 함)에 인접하여 저장되어 두 개의 동일한 4 바이트 행을 공유한다고 가정합니다. 구체적으로는 is_true이 0x1000에 있고 other_bool이 0x1001에 있다고 가정 해 봅시다. 두 값이 처음에 false이고 다른 스레드가 other_bool을 업데이트하려고 할 때 한 스레드가 is_true을 업데이트하기로 결정했다고 가정합니다. 후속하는 연산의 시퀀스가 ​​발생할 수

  • 스레드 1과 is_trueother_bool를 포함하는 4 바이트 값을로드하여 trueis_true 세트 준비. 스레드 1은 0x00000000을 읽습니다.
  • 스레드 2는 is_trueother_bool을 포함하는 4 바이트 값을로드하여 other_bool에서 true으로 설정합니다. 스레드 2는 0x00000000을 읽습니다.
  • 스레드 1은 is_true에 해당하는 4 바이트 값의 바이트를 업데이트하여 0x00000001을 생성합니다.
  • 스레드 2는 other_bool에 해당하는 4 바이트 값의 바이트를 업데이트하여 0x00000100을 생성합니다.
  • 스레드 1은 업데이트 된 값을 메모리에 저장합니다. is_true은 이제 true이고 other_boolfalse입니다.
  • 스레드 2는 업데이트 된 값을 메모리에 저장합니다. is_true은 이제 false이고 other_booltrue입니다. 이 is_true의 이전 값을 캡처 한 스레드 2, 덮어 쓰기 때문에

마지막에이 순서를 그 관찰, is_true에 업데이트가 끊어졌습니다.

바이트 단위 업데이트를 지원하고 매우 엄격한 메모리 모델을 가지고 있기 때문에 x86은 이러한 유형의 오류를 매우 용인합니다. 다른 Win32 프로세서는 용서할 수 없습니다. 예를 들어, RISC 칩은 바이트 단위 업데이트를 지원하지 않으며 대개 메모리 모델이 매우 약합니다.

+0

정렬에 대한 훌륭한 설명 귀하의 예제에서는 할당이 원자가 아닌 것을 명확하게 보여줍니다. –

+3

BTW는 컴파일러가 대상 아키텍처에 대해 스레드로부터 안전한 방식으로 변수를 정렬하도록합니다 (즉, 32 비트 x86 프로세서 용 4 바이트 셀의 'bool'값)? 문제를 해결할 수 있습니다. 컴파일러 중 일부가이 트릭을 수행합니까? –

+1

@PavelGatilov 최신 초안 표준에 따르면 컴파일러는이 동작을 방지해야합니다 . – curiousguy

7

아니요, 아니요 ..... 일종의 잠금 프리미티브를 사용해야합니다. 플랫폼에 따라 Boost를 사용하거나 InterlockedCompareExchange와 같은 기본 창으로 이동하는 경우 사용할 수 있습니다.

실제로 상황에 따라 스레드 안전 이벤트 메커니즘을 사용해야 할 수도 있습니다. 따라서 다른 스레드가 원하는 작업을 수행 할 수 있도록 '신호'할 수 있습니다.

+2

OP의 코드에는 무엇이 잘못 되었습니까? 무언가가 깨지는 시나리오를 고안해 낼 수 있습니까? –

+0

@KerrekSB, 실례합니다, OP? 그게 뭐야? – Alcott

+2

@Alcott : "OP"는 질문 한 사람과 마찬가지로 "원래의 포스터"를 의미합니다. 이 경우, 당신입니다. :-) – ruakh

-1

이 코드 조각의 스레드 안전성은 할당의 원 자성에 의존하지 않습니다. 두 스레드 루틴 모두 엄격하게 작동합니다. 경쟁 조건이 없습니다. thread_1은 특정 난수를 얻고 나서 '출력 섹션'을 떠나 다른 스레드가 그 스레드에서 작동하게 할 때까지 물건을 출력합니다. 주목할만한 몇 가지가 있지만 :

  • 랜드() 스레드 안전하지 될 수 있습니다 기능 (아니지만 여기에 주어진 코드의 문제)는 Win32 함수의 CreateThread를 사용해서는 안
  • () 특히 (잠재적으로) 전역 변수를 사용하는 CRT 라이브러리 함수를 사용할 때 특히 그렇습니다. 대신 _beginthreadex()를 사용하십시오.
+2

"두 스레드 루틴 모두 엄격하게 작동합니다 ._ "포옹 ? – curiousguy

4

모든 최신 프로세서에서 자연 정렬 된 기본 유형의 읽기 및 쓰기가 원 자성이라고 가정 할 수 있습니다. 메모리 버스가 적어도 읽거나 쓰여지는 유형만큼 넓은 경우, CPU는 단일 버스 트랜잭션에서 이러한 유형을 읽고 씁니다. 따라서 다른 스레드가 절반 완료 상태에서 볼 수 없게됩니다. x86 및 x64에서는 읽기 및 쓰기가 보장되지 않습니다. 8 바이트보다 작습니다. 즉, 스트리밍 SIMD 확장 (SSE) 레지스터의 16 바이트 읽기 및 쓰기 및 문자열 연산은 절대 아님을 의미합니다.

자연스럽게 정렬되지 않은 유형 (예 : 4 바이트 경계를 교차하는 DWORD 쓰기)의 읽기 및 쓰기는 원자적일 수 있습니다. CPU는 이러한 읽기 및 쓰기를 다중 버스 트랜잭션으로 수행해야 할 수 있습니다. 그러면 다른 스레드가 읽기 또는 쓰기 도중 데이터를 수정하거나 볼 수 있습니다.