2016-10-19 14 views
1

자물쇠가없는 프로그래밍으로 발을 찾으려고합니다. 메모리 순서 지정 의미에 대해 다른 설명을 읽은 다음, 가능한 재정렬이 일어날 수있는 일을 정리하고 싶습니다. 내가 아는 한, 컴파일러 (프로그램 컴파일시 최적화로 인해)와 CPU (런타임시)로 명령을 재정렬 할 수 있습니다. cpp reference는 다음의 예를 제공 이완 의미론잠금없는 프로그래밍 : 재정렬 및 ​​메모리 순서 의미

는 :

// Thread 1: 
r1 = y.load(memory_order_relaxed); // A 
x.store(r1, memory_order_relaxed); // B 
// Thread 2: 
r2 = x.load(memory_order_relaxed); // C 
y.store(42, memory_order_relaxed); // D 

이 X 및 Y는 초기에 제로와는 서열화되어 있지만 때문에 코드 R1 == R2는 == 42을 생성시킨다다고 - 스레드 1에서 B가되기 전에 C가 스레드 2에서 D보다 먼저 순서가 지정되면 D가 A의 수정 순서대로 나타나지 않고 B가 C보다 앞에 x가 수정 순서대로 나타나지 않습니다. 어떻게 그럴 수 있죠? C와 D가 재정렬됨에 따라 DABC가 실행 순서가됩니다. A와 B를 재주문 할 수 있습니까?

std::atomic<std::string*> ptr; 
int data; 

void producer() 
{ 
    std::string* p = new std::string("Hello"); 
    data = 42; 
    ptr.store(p, std::memory_order_release); 
} 

void consumer() 
{ 
    std::string* p2; 
    while (!(p2 = ptr.load(std::memory_order_acquire))) 
     ; 
    assert(*p2 == "Hello"); // never fires 
    assert(data == 42); // never fires 
} 

나는 우리가 편안한 메모리 순서 대신 취득을 사용하는 경우 어떻게 궁금하네요 다음 획득 출시 의미에 대한

다음 샘플 코드가

?전에 data의 값을 읽을 수 있었지만 p2은 어떨까요?

마지막으로,이 경우 편안한 메모리 주문을 사용하는 것이 좋습니다.

template<typename T> 
class stack 
{ 
    std::atomic<node<T>*> head; 
public: 
    void push(const T& data) 
    { 
     node<T>* new_node = new node<T>(data); 

     // put the current value of head into new_node->next 
     new_node->next = head.load(std::memory_order_relaxed); 

     // now make new_node the new head, but if the head 
     // is no longer what's stored in new_node->next 
     // (some other thread must have inserted a node just now) 
     // then put that new head into new_node->next and try again 
     while(!head.compare_exchange_weak(new_node->next, new_node, 
             std::memory_order_release, 
             std::memory_order_relaxed)) 
      ; // the body of the loop is empty 
    } 
}; 

나는 head.load(std::memory_order_relaxed)head.compare_exchange_weak(new_node->next, new_node, std::memory_order_release, std::memory_order_relaxed) 모두 의미한다.

위의 내용을 모두 요약하면 내 질문은 본질적으로 잠재적 인 재정렬에 언제 신경을 써야합니까?

+0

'int data'가 synchronizations없이 읽혀지면 # 2는 C++ 표준에 따라 정의되지 않은 동작을 가지므로주의해야합니다. 그것은 표준이 아니기 때문에 UB 종류의 데이터 경합이 될 것입니다 : 원자 적 유형. 'std :: atomic ''memory_order_relaxed'를 사용하면 UB'data'를 피할 수 있습니다. 그리고 단지 당신에게 정원 다양성 데이터 경주 버그를 가진 유효한 (그러나 덜 유용한) 프로그램을 남깁니다. 그러나'std :: string'은 원자/잠금없는 컨테이너가 아니기 때문에'ptr' 역 참조 이후의 문자열 비교는 여전히 데이터 경쟁이 될 것입니다. –

+0

@ PeterCordes 왜 문자열 비교가 데이터 경쟁이 될지 이해하지 못합니다. 소비자는'p2'가 초기화 될 때까지 기다리고 있습니다. 그런 다음 문자열을 비교합니다. "단일 스레드 코드를 중단하지 마십시오"규칙으로 인해이 순서를 보존해야합니다. 일단 우리가'p2'에서 뭔가를 얻었 으면 우리는 뾰족한 자료를 보게된다. (당신이 말한 것처럼, Alpha을 제외한 모든 CPU에서). 데이터는 적어도 포인터만큼 새 것이어야합니다. 따라서 포인터가 초기화되면 데이터도 이미 초기화되어 있어야합니다.아니면 내가 너를 오해 했니? – mentalmushroom

+0

필자 나 독자가'memory_order_relaxed'를 사용하면 데이터 경쟁이 일어났습니다. 왜냐하면'std :: string' 클래스의 데이터가 아직 쓰여지고있는 동안 포인터가 NULL이 아닌 것으로 읽을 수 있기 때문입니다. (알파가 아닌 CPU에 대한 컴파일은 UB와 관련이 없으며, 부호가있는 정수 오버플로와 마찬가지로 매우 잘 정의 된 의미를 가진 2의 보수 시스템에 대해 컴파일 할 때도 UB가됩니다.) –

답변

2

# 1의 경우 컴파일러는 x에서로드하기 전에 저장소에 y (종속성 없음)를 발급 할 수 있으며 그렇지 않은 경우 x에서로드가 CPU/메모리 수준으로 지연 될 수 있습니다.

# 2의 경우 p2는 0이 아니지만 * p2도 데이터도 반드시 의미있는 값을 갖지 않습니다. # 3

이 스레드에 의해 비 원자 매장을 게시 단 하나 개의 행동이, 그리고 당신은 재정렬, 또는 더 나은, 어떤 순서 가정하지 신경 항상해야 릴리스

입니다 :도 C++를 하드웨어도 위에서 아래로 코드를 실행하지 않으며 의존성 만 존중합니다.

+0

포인트 # 2에 대해 자세히 설명해 주시겠습니까? * p2는 의미가 없습니다. 포인트 3에 관해서는, 나는 출판을 위해 릴리즈를 사용한다는 것을 이해하지만,로드 (new_node-> next 로의 초기로드와 compare_exchange_weak' 실패의 경우로드)에 대한 더 많은 질문이있었습니다. 왜 우리가 여기서 주문을 받아야하지 않니? – mentalmushroom

+0

@mentalmushroom'* p2'는 편안하게 읽을 수있는 스레드를 위해 공개되지 않았습니다. 그것은 누구든지 취득한 것으로 출판되었습니다. # 3에는 메모리에서 읽혀지는 비 원자가 없기 때문에 아무 것도 얻을 수 없습니다. – Cubbi

+0

편안한 독자를 위해 출판되지 않았다면 왜'ptr.load()'가 0이 아닌 값을 반환 했습니까? # 3에서'new_node'와'new_node-> next'는 비 원자 적입니다, 맞습니까? – mentalmushroom