2012-08-31 3 views
3

내가 설명에 따르면 앤서니 윌리엄스의 책 "C++ 동시성"이상한 결과에 대한 C++ 11 메모리 모델 (편안 순서)

#include<atomic> 
#include<thread> 
#include<cassert> 

std::atomic_bool x,y; 
std::atomic_int z; 

void write_x_then_y() { 
    x.store(true, std::memory_order_relaxed); 
    y.store(true, std::memory_order_relaxed); 
} 

void read_y_then_x() { 
    while(!y.load(std::memory_order_relaxed)); 
    if(x.load(std::memory_order_relaxed)) { 
    ++z; 
    } 
} 

int main() { 
    x = false; 
    y = false; 
    z = 0; 
    std::thread a(write_x_then_y); 
    std::thread b(read_y_then_x); 
    a.join(); 
    b.join(); 
    assert(z.load()!=0); 
} 

의 메모리 모델의 예제를 테스트하고, 차이에 편안한 작업 변수 (여기 x 및 y)는 자유롭게 재정렬 할 수 있습니다. 그러나 나는 며칠 이상 문제를 반복적으로 실행했다. 나는 어설 션 (assert (z.load()! = 0);)이 발생한다는 상황에 결코 부딪치지 않았다. 난 그냥 기본 최적화를 사용하고 코드를 사용하여 컴파일 g + + -std = C++ 11 -lpthread dataRaceAtomic.cpp 아무도 실제로 그것을 시도하고 어설 션을 누르십니까? 누구든지 내 시험 결과에 대한 설명을 주실 수 있습니까? BTW, 나는 또한 원자형을 사용하지 않고 버전을 시도, 나는 같은 결과를 얻었다. 현재 두 프로그램 모두 정상적으로 실행 중입니다. 감사.

+0

내가 올바르게 당신을 이해한다면, 당신은 다른 가게가 재정렬되지 않았다고 불평하고 있습니까? –

+3

수 있습니다! = 될 것입니다. 특히 언어가 * 모델 *이기 때문에 모델의 구현이 "이러한 작업은 결코 재정렬되지 않을 것"과 같은 보증을 가질 수 있습니다. – GManNickG

+0

아직 컴파일러가 메모리 모델을 지원하지 않습니다. – inf

답변

8

이 값은 실행중인 프로세서 유형에 따라 다를 수 있습니다.

x86에는 다른 프로세서처럼 느슨한 메모리 모델이 없습니다. 특히, 상점은 다른 상점과 관련하여 재정렬되지 않습니다.

http://bartoszmilewski.com/2008/11/05/who-ordered-memory-fences-on-an-x86/에는 x86 메모리 모델에 대한 자세한 정보가 있습니다.

+0

내가 실행중인 CPU는 X86 아키텍처 인 Intel (R) Xeon (R) CPU E5540입니다. 이 문제를 지적 해 주신 Michael @에게 감사드립니다. –

+0

@Michael, 이는 x86 아키텍처 CPU가 메모리 주문이 강하기 때문에 메모리 주문을 완화 할 수 없다는 뜻입니까? –

2

원자, 메모리 순서 및 테스트에 대한 몇 가지.

먼저 예제는 그림입니다. 당신은 그것을 읽고 생각해야합니다. 실제 스레드에서 새로운 스레드를 시작하는 오버 헤드는 read_y_then_x이 시작되기 훨씬 전에 실행을 마쳤으므로이 두 스레드를 반복적으로 실행하는 테스트 프로그램에서는 실제로 순서가 변경되지 않습니다. 멀티 스레드 코드 테스트의 멋진 세계에 오신 것을 환영합니다!

두 번째, 고려해야 할 두 가지 순서 재 지정 문제가 있습니다.

먼저 컴파일러는 소스 코드와 다른 순서로 항목을 저장하거나 읽는 코드를 생성 할 수 있습니다. 이는 멀티 스레딩이 없을 때 유효한 최적화이며, 중요한 것입니다. 반면에 여러 스레드를 도입하면 저장 순서와 읽기 순서가 중요 할 수 있습니다. 결과적으로 새로운 C++ 메모리 모델은 상점 및로드를 이동할 수없는시기를 지정합니다. 특히 원자 접근을 통해 이동할 수 없습니다. 이것은 당신에게 추론 할 수있는 고정 된 지점을 제공합니다.이 원자 저장소를 만들기 전에 저는이 비 원자 저장소를 했으므로 컴파일러가 두 번째 전에 첫 번째 작업을 수행 할 것입니다.

두 번째로 하드웨어은 상점 및로드를 재정렬 할 수 있습니다. 이것은 일반적으로 프로세서의 캐싱 전략의 결과이며 "가시성"이라고합니다. 한 스레드의 변수에 대한 변경 사항은 첫 번째 스레드가 작성한 후에 해당 변수를 읽는 다른 스레드가 반드시 볼 수있는 것은 아닙니다. 왜냐하면 두 개의 스레드가 두 개의 개별 프로세서에서 실행될 수 있기 때문입니다. 새 값이 주 메모리에 기록되지 않거나 다른 프로세서가 캐시에 이전 값을 가지고있는 경우 두 번째 스레드는 변경 사항을 볼 수 없습니다. Atomics는 값이 표시되는 시점에 대한 규칙을 제공합니다 (캐시에서 주 메모리로 쓰기가 플러시되어야하고 읽기가 캐시 대신 주 메모리로 이동해야하는 경우로 변환됩니다). 그것이 바로이 예가있는 것입니다. 그리고 @Michael이 말했듯이, 가치가 가시화 될 필요가 없기 때문에 그것이 가능할 수 없다는 것을 의미하지는 않습니다. 일부 프로세서는 이러한 종류의 일을 허용하는 약한 메모리 모델을 가지고 있으며, 속도 향상과 명확한 복잡성으로 인해 수행 할 작업을 분석 할 때 일부 프로세서는 그렇지 않습니다. x86은 후자의 범주에 속합니다. 거의 모든 가시성 제약 조건을 허용하더라도 순차적으로 일관성이 유지됩니다.

+0

Thanks @ Pete. 스레드를 생성하는 오버 헤드로 인해 프로그램은 항상 고정 된 순서로 실행됩니다. 컴파일러 재정렬 문제의 경우 gcc optimization (http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html)의 옵션에 따라 O2부터 컴파일러가 명령 재정렬 최적화를 수행 할 것이라고 지시했습니다. 낮은 최적화를 사용하면 컴파일러 순서 변경을 피할 수 있습니다. 하드웨어 재주문을 위해서는 좋은 멀티 스레딩 프로그램을 작성하기 위해 동일한 기능을 구현하는 데 차이를 둘 수 있습니까? –

+0

@Dancing_bunny - 죄송합니다. 컴파일러가 원자 적로드 나 저장소 (또는 스레드 생성, 뮤텍스 잠금 등)를 가로 질러 물건을 이동하도록 허용되어 있지 않으므로 일반적으로 걱정할 필요가 없습니다 . -O2는 괜찮을거야. –

0

x와 y를 재정렬 할 수 있기 때문에 컴파일러에서 불확정 동작을 생성하지 않아도됩니다.

x.store(false, memory_order_relaxed); // redundant store 
y.store(true, memory_order_relaxed); 
x.store(true, memory_order_relaxed); 

x가 true로 표시되면 y가 true 여야합니다. 그러나 컴파일러는이를 재정렬 할 수 있습니다.

x.store(false, memory_order_relaxed); // redundant store 
x.store(true, memory_order_relaxed); 
y.store(true, memory_order_relaxed); 

선택할 수 있습니까? 이 경우 아마 그렇지 않을 것입니다. xyx 패턴에서 최적의 코드를 생성하는 것은 컴파일러에서 쉽습니다. 그러나 더 복잡한 경우에, 더 많은 일들이 진행되면, 재정렬은 컴파일러가 더 많은 값을 레지스터에 넣을 수있게합니다.

0

메모리 모델은 가능한 것을 설명하며, 실제로 순서가 발생할 것이라는 보장은 없습니다. 컴파일러와 하드웨어에 따라 다릅니다. 허용되는 동작을 철저하게 탐색하려면 CDSChecker와 같은 도구를 사용하십시오.