프로그래머 수준에서 C ++ std :: atomic으로 보장되는 것은 무엇입니까?


9

에 대한 여러 기사, 대화 및 스택 오버플로 질문을 듣고 읽었으며 std::atomic그것을 잘 이해하고 싶습니다. MESI (또는 파생) 캐시 일관성 프로토콜, 버퍼 저장, 대기열 무효화 등의 가능한 지연으로 인해 캐시 라인 쓰기 가시성과 약간 혼동되기 때문에.

x86은 더 강력한 메모리 모델을 가지고 있으며 캐시 무효화가 지연되면 x86이 시작된 작업을 되돌릴 수 있음을 읽었습니다. 그러나 이제는 플랫폼과 독립적으로 C ++ 프로그래머로 가정 해야하는 것에 대해서만 관심이 있습니다.

[T1 : 스레드 1 T2 : 스레드 2 V1 : 공유 원자 변수]

나는 std :: atomic이 그것을 보증한다는 것을 이해합니다.

(1) 변수에서 데이터 레이스가 발생하지 않습니다 (캐시 라인에 독점적으로 액세스 할 수 있기 때문에).

(2) 우리가 사용하는 memory_order에 따라 (방벽 이전, 장벽 후 또는 둘 다) 순차적 일관성이 발생한다는 것을 보장합니다 (장벽 포함).

(3) T1의 원자 쓰기 (V1) 후에 T2의 원자 RMW (V1)가 일관됩니다 (캐시 라인이 T1의 기록 된 값으로 업데이트 됨).

그러나 캐시 일관성 입문서에서 언급했듯이

이러한 모든 것의 의미는 기본적으로로드가 오래된 데이터를 가져올 수 있다는 것입니다 (해당 무효화 요청이 무효화 대기열에있는 경우).

따라서 다음이 맞습니까?

(4) std::atomic는 T2가 T1의 원자 쓰기 (V) 후에 원자 판독 (V)에서 '스톨'값을 읽지 않는다고 보장하지 않습니다.

(4)가 맞는지에 대한 질문 : T1의 원자 쓰기가 지연과 상관없이 캐시 라인을 무효화하는 경우 원자 RMW가 작동하지만 원자 판독이 아닌 경우 무효화가 유효하기를 기다리는 이유는 무엇입니까?

(4)가 틀린 경우 질문 : 스레드가 언제 실행에서 'stale'값을 읽을 수 있고 "보일 수 있습니까"?

나는 당신의 답변을 많이 주셔서 감사합니다

업데이트 1

그래서 내가 (3)에 잘못 된 것 같습니다. 초기 V1 = 0에 대해 다음 인터리브를 상상해보십시오.

T1: W(1)
T2:      R(0) M(++) W(1)

이 경우 T2의 RMW가 W (1) 이후에 완전히 발생한다고 보장되지만 여전히 'stale'값을 읽을 수 있습니다 (잘못된). 이것에 따르면 atomic은 전체 캐시 일관성을 보장하지 않으며 순차적 일관성 만 보장합니다.

업데이트 2

(5) 이제이 예제를 상상해보십시오 (x = y = 0이고 원자 적입니다).

T1: x = 1;
T2: y = 1;
T3: if (x==1 && y==0) print("msg");

우리가 이야기 한 바에 따르면, 화면에 "msg"가 표시되는 것은 T1 이후에 T2가 실행 된 것 이상의 정보를 제공하지 않습니다. 따라서 다음 중 하나가 실행되었을 수 있습니다.

  • T1 <T3 <T2
  • T1 <T2 <T3 (여기서 T3은 x = 1이지만 y = 1은 아님)

맞습니까?

(6) 스레드가 항상 'stale'값을 읽을 수있는 경우 일반적인 "게시"시나리오를 취했지만 일부 데이터가 준비되었다는 신호를 보내는 대신 반대로 데이터를 삭제하면 어떻게됩니까?

T1: delete gameObjectPtr; is_enabled.store(false, std::memory_order_release);
T2: while (is_enabled.load(std::memory_order_acquire)) gameObjectPtr->doSomething();

여기서 T2는 is_enabled가 false임을 확인할 때까지 삭제 된 ptr을 계속 사용합니다.

(7) 또한 스레드가 'stale'값을 읽을 수 있다는 사실은 뮤텍스 를 하나의 잠금없는 원자 권리로 구현할 수 없다는 것을 의미 합니까? 스레드 간 동기화 메커니즘이 필요합니다. 잠금 가능한 원자가 필요합니까?

답변:


3
  1. 예, 데이터 경쟁이 없습니다
  2. 예, 적절한 memory_order값을 사용하면 순차적 일관성을 보장 할 수 있습니다
  3. 원자 읽기-수정-쓰기는 항상 동일한 변수에 원자 쓰기 전 또는 후에 완전히 발생합니다.
  4. 예, T2는 T1에 원자 쓰기 후 변수에서 오래된 값을 읽을 수 있습니다

원자 적 읽기-수정-쓰기 작업은 원 자성을 보장하는 방식으로 지정됩니다. 다른 스레드가 초기 읽기 후 및 RMW 작업 쓰기 전에 값에 쓸 수 있으면 해당 작업은 원 자성이 아닙니다.

스레드는 상대 순서 를 보장하기 전에 발생하는 경우를 제외하고 항상 오래된 값을 읽을 수 있습니다 .

RMW 조작이 "stale"값을 읽는 경우, 생성 된 쓰기가 읽은 값을 겹쳐 쓰는 다른 스레드에서 쓰기 전에 볼 수있게합니다.

예를 들어 업데이트

T1이 쓰고 x=1T2가 처음에 0을 x++가진 x경우 스토리지의 관점에서 선택 사항 x은 다음 과 같습니다.

  1. T1의 쓰기가 먼저이므로 T1은을 쓴 x=1다음 T2는을 읽고 x==12로 증가 x=2시키고 단일 원자 연산으로 다시 씁니다 .

  2. T1의 쓰기는 두 번째입니다. T2는을 읽고 x==01로 증가 x=1시키고 단일 작업으로 다시 쓴 다음 T1을 씁니다 x=1.

그러나이 두 스레드간에 다른 동기화 지점이 없으면 스레드는 메모리로 플러시되지 않은 작업을 진행할 수 있습니다.

따라서 x=1T2는 여전히을 읽고 x==0쓰기 때문에 다른 것을 계속 진행할 수 있습니다 x=1.

다른 x동기화 지점이 있으면 동기화 지점이 순서를 강제하기 때문에 어떤 스레드가 먼저 수정되었는지가 분명해 집니다.

이는 RMW 조작에서 읽은 값에 대한 조건이있는 경우에 가장 분명합니다.

업데이트 2

  1. memory_order_seq_cst모든 원자 연산에 (기본값)을 사용 하면 이런 종류의 것에 대해 걱정할 필요가 없습니다. 프로그램의 관점에서 "msg"가 표시되면 T1이 실행 된 다음 T3, T2가 실행 된 것입니다.

다른 메모리 순서 (특히 memory_order_relaxed)를 사용하면 코드에 다른 시나리오가 나타날 수 있습니다.

  1. 이 경우 버그가 있습니다. is_enabledT2가 while루프에 들어갈 때 플래그가 true 라고 가정하여 본문을 실행하기로 결정합니다. T1은 이제 데이터를 삭제하고 T2는 매달려 포인터 인 포인터를 연기 하고 정의되지 않은 동작 이 발생합니다. 원자는 깃발의 데이터 경쟁을 막는 것 이상으로 어떤 식 으로든 도움을 주거나 방해하지 않습니다.

  2. 단일 원자 변수로 뮤텍스를 구현할 수 있습니다 .


빠른 답변을 주신 @Anthony Wiliams에게 감사드립니다. 'stale'값을 읽는 RMW의 예로 내 질문을 업데이트했습니다. 이 예제를 보면 상대 순서에 의해 무엇을 의미하며 쓰기 전에 T2의 W (1)이 표시됩니까? T2가 T1의 변경 사항을 본 후에는 더 이상 T2의 W (1)을 읽지 않을 것입니까?
Albert Caldas

따라서 "Threads가 항상 오래된 값을 읽을 수있다"는 것은 캐시 일관성이 보장되지 않는다는 것을 의미합니다 (적어도 c ++ 프로그래머 수준에서). 내 update2를 보시겠습니까?
Albert Caldas

이제 언어와 하드웨어 메모리 모델에 더 많은 관심을 기울여 모든 것을 완전히 이해해야한다는 것을 알았습니다. 고마워요!
Albert Caldas

1

(3)에 관해서는-사용 된 메모리 순서에 따라 다릅니다. 상점과 RMW 작업이 std::memory_order_seq_cst모두 사용하는 경우 두 작업 모두 어떤 방식 으로든 순서가 정해집니다. 즉, 상점이 RMW 전에 발생하거나 다른 방식으로 진행됩니다. 상점이 RMW 이전에 주문 된 경우 RMW 조작이 저장된 값을 "인식"하는 것이 보장됩니다. RMW 이후에 상점을 주문하면 RMW 조작으로 작성된 값을 겹쳐 씁니다.

보다 완화 된 메모리 순서를 사용하는 경우 수정은 여전히 ​​어떤 방식 으로든 순서화되지만 (변수의 수정 순서) RMW 조작이 있더라도 RMW가 저장 조작의 값을 "인식"하는지에 대한 보장은 없습니다. 순서입니다 변수의 수정 순서로 쓰기.

또 다른 기사를 읽고 싶다면 C / C ++ 프로그래머를위한 메모리 모델을 참조하십시오 .


기사를 읽어 주셔서 감사합니다. 아직 읽지 않았습니다. 나이가 많더라도 내 아이디어를 정리하는 것이 유용했습니다.
Albert Caldas

1
이 기사는 내 석사 논문에서 약간 확장되고 수정 된 장입니다. :-) C ++ 11에 도입 된 메모리 모델에 중점을 둡니다. C ++ 14/17에 도입 된 (작은) 변경 사항을 반영하도록 업데이트 할 수 있습니다. 개선에 대한 의견이나 제안이 있으면 알려주십시오!
시인
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.