C ++ 11에서 StoreLoad 장벽을 달성하는 방법?


13

고전적인 문제의 변형을 해결하는 휴대용 코드 (Intel, ARM, PowerPC ...)를 작성하고 싶습니다.

Initially: X=Y=0

Thread A:
  X=1
  if(!Y){ do something }
Thread B:
  Y=1
  if(!X){ do something }

하는 목표는 모두 스레드가 수행되는 상황을 방지하는 것입니다something . (아무것도 실행되지 않으면 괜찮습니다. 이것은 한 번만 실행되는 메커니즘이 아닙니다.) 아래의 내 추론에 약간의 결함이 있으면 나를 수정하십시오.

나는 다음과 같이 memory_order_seq_cstatomic storeloads로 목표를 달성 할 수 있음을 알고 있습니다.

std::atomic<int> x{0},y{0};
void thread_a(){
  x.store(1);
  if(!y.load()) foo();
}
void thread_b(){
  y.store(1);
  if(!x.load()) bar();
}


{x.store(1), y.store(1), y.load(), x.load()}프로그램 순서 "에지 (edge)"와 일치해야하는 이벤트 에 총 단일 순서가 있어야하기 때문에 목표를 달성합니다 .

  • x.store(1) "TO TO 이전" y.load()
  • y.store(1) "TO TO 이전" x.load()

foo()부름을 받았다 면 추가 우위가 있습니다.

  • y.load() "전에 가치를 읽는다" y.store(1)

bar()부름을 받았다 면 추가 우위가 있습니다.

  • x.load() "전에 가치를 읽는다" x.store(1)

이 모든 모서리가 함께 결합되어 사이클을 형성합니다.

x.store(1)"TO TO before before" y.load()"전에 값을 읽습니다 y.store(1)."TO TO before before " x.load()"전에 값을 읽습니다 "x.store(true)

주문에주기가 없다는 사실을 위반합니다.

나는 의도적으로 비표준 용어를 "TO TO before before"와 같은 표준 용어와 달리 "Before value before"를 사용합니다 happens-before. 왜냐하면 이러한 에지가 실제로 happens-before관계를 암시한다는 가정의 정확성에 대한 피드백을 요청하고 싶기 때문입니다. 이러한 결합 그래프의주기는 금지되어 있습니다. 확실하지 않습니다. 내가 아는 것은이 코드가 Intel gcc & clang 및 ARM gcc에서 올바른 장벽을 생성한다는 것입니다


이제 실제 문제는 "X"를 제어 할 수 없기 때문에 조금 더 복잡합니다. 일부 매크로, 템플릿 등에 숨겨져 있고보다 약할 수 있습니다. seq_cst

"X"가 단일 변수인지 아니면 다른 개념 (예 : 가벼운 세마포어 또는 뮤텍스)인지조차 모르겠습니다. 내가 아는 것은 두 개의 매크로가 set()있고 다른 스레드가 호출 한 후 "후" check()check()반환 한다는 것 입니다. (그것은 되어 도하는 것으로 알려져 및 스레드 안전 및 데이터 레이스 UB를 만들 수 없습니다.)trueset()setcheck

개념적으로 set()는 "X = 1"과 check()비슷하고 "X"와 비슷하지만 관련된 원자에 직접 접근 할 수는 없습니다.

void thread_a(){
  set();
  if(!y.load()) foo();
}
void thread_b(){
  y.store(1);
  if(!check()) bar();
}

나는 걱정하고있어 set()내부적으로 구현 될 수 x.store(1,std::memory_order_release)및 / 또는 check()수 있습니다 x.load(std::memory_order_acquire). 또는 std::mutex한 스레드가 잠금 해제되고 다른 스레드가 잠금 해제된다는 가정 try_lock이 있습니다. ISO 표준 std::mutex에서는 seq_cst가 아닌 획득 및 릴리스 순서 만 보장합니다.

이 경우 check()이전에 "본문을 다시 정렬"할 수있는 경우입니다 y.store(true)( Alex의 답변을 참조하십시오 ( PowerPC에서 발생 함 )).
이 이벤트 시퀀스가 ​​가능하므로 이것은 실제로 나쁠 것입니다.

  • thread_b()먼저 x( 0) 의 이전 값을로드합니다.
  • thread_a() 포함하여 모든 것을 실행 foo()
  • thread_b() 포함하여 모든 것을 실행 bar()

그래서, 모두 foo()와는 bar()내가 피할 수 있던라고있어. 이를 방지 할 수있는 옵션은 무엇입니까?


옵션 A

Store-Load 장벽을 강제로 시도하십시오. 실제로 이것은 Alex가 다른 답변에서std::atomic_thread_fence(std::memory_order_seq_cst); 설명한 것처럼 모든 테스트 된 컴파일러가 완전한 울타리를 방출 했다고 설명합니다 .

  • x86_64 : MFENCE
  • PowerPC : hwsync
  • 이타 누임 : MF
  • ARMv7 / ARMv8 : dmb ish
  • MIPS64 : 동기화

이 접근법의 문제점은 C ++ 규칙에서 std::atomic_thread_fence(std::memory_order_seq_cst)완전한 메모리 장벽으로 변환해야 한다는 보장을 찾을 수 없다는 것 입니다. 실제로, atomic_thread_fenceC ++에서 s의 개념은 메모리 장벽의 조립 개념과는 다른 추상화 수준 인 것으로 보이며 "어떤 원자 연산이 무엇과 동기화되는지"와 같은 것들을 더 다루고 있습니다. 아래 구현이 목표를 달성했다는 이론적 증거가 있습니까?

void thread_a(){
  set();
  std::atomic_thread_fence(std::memory_order_seq_cst)
  if(!y.load()) foo();
}
void thread_b(){
  y.store(true);
  std::atomic_thread_fence(std::memory_order_seq_cst)
  if(!check()) bar();
}

옵션 B

Y에 대한 read-modify-write memory_order_acq_rel 작업을 사용하여 동기화를 달성하기 위해 Y에 대한 제어를 사용하십시오.

void thread_a(){
  set();
  if(!y.fetch_add(0,std::memory_order_acq_rel)) foo();
}
void thread_b(){
  y.exchange(1,std::memory_order_acq_rel);
  if(!check()) bar();
}

여기서 아이디어는 단일 원자 ( y) 에 대한 액세스는 모든 관찰자가 동의하는 단일 순서를 형성해야하므로 fetch_add이전 exchange또는 그 반대입니다.

fetch_add이전 인 경우 exchange"release"부분은 fetch_add"acquire"부분과 동기화 exchange되므로 set()코드 실행시 모든 부작용을 볼 수 있어야 check()하므로 bar()호출되지 않습니다.

그렇지 않으면, exchange이전이다 fetch_add다음은 fetch_add1과 전화하지 foo(). 따라서 foo()와를 모두 호출하는 것은 불가능합니다 bar(). 이 추론이 맞습니까?


옵션 C

더미 원자를 사용하여 재난을 방지하는 "가장자리"를 도입하십시오. 다음 접근법을 고려하십시오.

void thread_a(){
  std::atomic<int> dummy1{};
  set();
  dummy1.store(13);
  if(!y.load()) foo();
}
void thread_b(){
  std::atomic<int> dummy2{};
  y.store(1);
  dummy2.load();
  if(!check()) bar();
}

여기에 문제 atomic가 국지적 이라고 생각하면 다음과 같은 추론에서 문제를 전역 범위로 옮기는 것을 상상해보십시오. 나는 의도적으로 더미가 얼마나 재미 있는지 노출시키는 방식으로 코드를 썼습니다. dummy2는 완전히 분리되어 있습니다.

왜 지구상에서 이것이 효과가 있을까요? 글쎄, {dummy1.store(13), y.load(), y.store(1), dummy2.load()}프로그램 순서 "에지"와 일치 해야하는 단일 전체 순서가 있어야합니다 .

  • dummy1.store(13) "TO TO 이전" y.load()
  • y.store(1) "TO TO 이전" dummy2.load()

(seq_cst store + load는 StoreLoad를 포함한 전체 메모리 장벽과 동등한 C ++을 형성하기를 희망합니다. 별도의 장벽 명령이 필요하지 않은 AArch64를 포함한 실제 ISA에서 asm에서 asm에서와 마찬가지로)

이제 우리는 두 가지 경우를 고려해야합니다. 총 순서 y.store(1)이전 y.load()또는 이후입니다.

경우 y.store(1)이전 인 y.load()다음 foo()이라고 우리는 안전하지 않습니다.

경우 y.load()이전이다 y.store(1), 우리는 이미 프로그램 순서에서이 두 가장자리과 결합, 우리는 것을 추론 :

  • dummy1.store(13) "TO TO 이전" dummy2.load()

이제,이 dummy1.store(13)효과를 해제 릴리스 동작입니다 set(), 그리고 dummy2.load()그래서, 획득 작업입니다 check()의 효과를 볼 수 set()있어 bar()호출되지 않습니다 우리는 안전합니다.

check()그 결과를 볼 것이라고 생각하는 것이 맞 set()습니까? 이렇게 다양한 종류의 "가장자리"( "순서 순서", "전체 순서", "해제 전", "취득 후")를 결합 할 수 있습니까? 나는 이것에 대해 심각한 의문을 가지고 있습니다 : C ++ 규칙은 같은 위치에서 상점과로드 사이의 "동기화"관계에 대해 이야기하는 것 같습니다-여기에는 그러한 상황이 없습니다.

우리는 seq_cst 총 순서에서 (다른 추론을 통해 dumm1.store) 알려진 것으로 알려진 경우에만 걱정합니다 dummy2.load. 따라서 동일한 변수에 액세스 한 경우로드에 저장된 값이 표시되고 동기화됩니다.

(atomic loads 및 store가 적어도 일방 메모리 장벽으로 컴파일되는 구현에 대한 메모리 배리어 / 리오 더링 추론 (및 seq_cst 작업은 순서를 바꿀 수 없습니다 : 예를 들어 seq_cst store는 seq_cst로드를 통과 할 수 없음)은 모든로드 / 이후 dummy2.load에 다른 스레드에 확실히 표시되고 나중에 다른 스레드에 y.store대해서도 유사하게 저장됩니다 y.load.


https://godbolt.org/z/u3dTa8 에서 옵션 A, B, C를 구현할 수 있습니다.


1
C ++ 메모리 모델에는 StoreLoad 재정렬 개념이 없으며 동기화 만 가능합니다. (그리고 실제 하드웨어의 asm과는 달리 비원 자적 객체에 대한 데이터 경쟁에 대한 UB) 알고있는 모든 실제 구현 std::atomic_thread_fence(std::memory_order_seq_cst)에서 완전한 장벽으로 컴파일하지만 전체 개념이 구현 세부 사항이므로 찾을 수는 없습니다. 표준에 대한 언급. (CPU 메모리 모델은 일반적 된다 reorerings 순차적 일관성에 대해 허용되는 용어의 정의이다 예 86 - SEQ CST + 스토어 버퍼 w / 포워딩.)
피터 코르

@PeterCordes에게 감사합니다. 저의 글은 명확하지 않았을 것입니다. "Option A"섹션에서 작성한 내용을 전달하고 싶었습니다. 내 질문의 제목에 "StoreLoad"라는 단어가 사용되고 "StoreLoad"는 완전히 다른 세계의 개념이라는 것을 알고 있습니다. 내 문제는이 개념을 C ++에 매핑하는 방법입니다. 또는 직접 매핑 할 수없는 경우 내가 목표를 달성하는 방법 : 방지 foo()bar()호출 하지 마십시오 .
qbolec

1
compare_exchange_*원자 부울에서 값을 변경하지 않고 RMW 작업을 수행하는 데 사용할 수 있습니다 (예상 값과 새 값을 같은 값으로 간단히 설정).
시인

1
@Fareanor 및 qbolec : atomic<bool>has exchangecompare_exchange_weak. 후자는 CAS (true, true) 또는 false, false를 시도하여 더미 RMW를 수행하는 데 사용될 수 있습니다. 실패하거나 원자 적으로 값을 자체로 바꿉니다. (x86-64 asm에서, 그 트릭 lock cmpxchg16b은 16 바이트로드를 보장하는 방법입니다. 비효율적이지만 별도의 잠금을 취하는 것보다 덜 나쁩니다.)
Peter Cordes

1
@PeterCordes 네, 전화 도 foo()받지 bar()않을 수도 있습니다. 나는 코드의 많은 "실제 세계"요소들을 가져오고 싶지 않았다. 그러나 하나가 정말 배경 층이 무엇인지 알 필요가있는 경우 : set()정말 some_mutex_exit(), check()있다 try_enter_some_mutex(), y"일부 웨이터가"되어 foo()"사람을 깨어하지 않고 종료"입니다, bar()나는 거부 "wakup에 대한 대기"입니다 ...하지만 여기에서이 디자인에 대해 토론하십시오 – 나는 그것을 바꿀 수 없습니다.
qbolec

답변:


5

옵션 A와 B는 유효한 솔루션입니다.

  • 옵션 A : seq-cst 펜스가 무엇을 번역하는지는 중요하지 않습니다. C ++ 표준은 그것이 무엇을 보장하는지 명확하게 정의합니다. 나는이 게시물에 배치했다 : memory_order_seq_cst 울타리는 언제 유용합니까?
  • 옵션 B : 네, 당신의 추론은 정확합니다. 일부 객체의 모든 수정은 단일 순서 (수정 순서)를 가지므로이를 사용하여 스레드를 동기화하고 모든 부작용의 가시성을 확보 할 수 있습니다.

그러나 옵션 C는 유효 하지 않습니다! 동기화 대상 관계는 동일한 객체에 대한 획득 / 릴리스 작업 으로 만 설정할 수 있습니다 . 귀하의 경우에는 완전히 다른 두 개의 독립된 객체 dummy1dummy2있습니다. 그러나 이것들은 이전에 일어난 관계를 확립하는 데 사용될 수 없습니다. 사실, 원자 변수는 순전히 지역적이므로 (즉, 하나의 스레드 만 건드 리므로) 컴파일러는 as-if 규칙에 따라 변수를 자유롭게 제거 할 수 있습니다 .

최신 정보

옵션 A :
나는 약간의 원자 가치를 가정 set()하고 check()운영합니다. 그런 다음 다음과 같은 상황이 있습니다 (->는 시퀀스 전을 나타냅니다 ).

  • set()-> fence1(seq_cst)->y.load()
  • y.store(true)-> fence2(seq_cst)->check()

따라서 다음 규칙을 적용 할 수 있습니다.

원자 조작은 와 B 원자 오브젝트에 M , 수정은 M은B가 존재한다면, 그 값을 취 울타리 XY 되도록 A가 전에 서열화되어 X , Y는 전에 서열화 BX는 앞에 Y를S , 그런 다음 B 는 수정 순서에 따라 A 의 영향 또는 나중에 M의 수정을 관찰합니다 .memory_order_seq_cst

즉,에 check()저장된 값을 set보거나 y.load()쓰여진 값을 볼 수 있습니다 y.store()(에 대한 작업 y도 사용할 수 있음 memory_order_relaxed).

옵션 C : C ++ 17 개 표준 상태 [32.4.3, p1347]

영향을받는 모든 위치에 대한 "이전에 발생하는"주문 및 수정 주문과 일치 하는 모든 작업 에 대해 단일 총 주문 S 가 있어야합니다 memory_order_seq_cst.

여기서 중요한 단어는 "일관 적"입니다. 이 동작하면 것을 의미한다 A가 발생-전에 동작 B 후, A가 선행되어야 B를S . 그냥 작동하기 때문에 우리가하지 역 추론 할 수 있도록하지만, 논리적 의미는 단방향-거리이며, C의 선행 동작 D 에서 S는 것을 의미하지는 않습니다 C가 되기 전에 발생 D .

특히, 두 개의 개별 오브젝트에 대한 두 개의 seq-cst 조작을 사용하여 S가 완전히 순서화되어 있어도 관계 전에 발생을 설정할 수 없습니다. 별도의 오브젝트에 대한 조작 을 주문하려면 seq-cst를 참조해야합니다. -울타리 (옵션 A 참조).


옵션 C가 유효하지 않다는 것은 확실하지 않습니다. 개인 객체에서도 seq-cst 작업은 다른 작업을 어느 정도 정렬 할 수 있습니다. 동기화 기능은 없지만 동의하지는 않지만 foo 또는 bar 실행 중 어느 것이 든 상관하지 않으며 둘 다 실행 되지 않습니다 . 순서대로 된 관계와 seq-cst 작업의 총 순서 (존재해야 함)는 우리에게 그것을 제공한다고 생각합니다.
Peter Cordes

@mpoeter 감사합니다. 옵션 A에 대해 자세히 설명해 주시겠습니까? 답변의 3 가지 글 머리표 중 어느 것이 여기에 적용됩니까? IIUC의 y.load()효과가 보이지 않으면 y.store(1)S에서 atomic_thread_fencethread_a의 S 가 thread_b 이전 atomic_thread_fence임을 알 수 있습니다. 내가 보지 못하는 것은 set()부작용을 볼 수 있다는 결론을 내릴 수있는 방법 check()입니다.
qbolec

1
@qbolec : 옵션 A에 대한 자세한 내용으로 답변을 업데이트했습니다.
mpoeter

1
예, 로컬 seq-cst 작업은 여전히 모든 seq-cst 작업 에서 단일 총 주문 S 의 일부입니다 . 그러나 S는 "전용"입니다 (가) 순서 및 수정 주문 전에-발생과 일치 , 즉, 경우 A가 발생-전에 B 후, A가 선행되어야 B를S . 그러나 역은해서, 즉, 보장되지 선행의 B 에서 S는 , 우리가 하지 추론 할 수 있다는 A가 발생-전에 B .
시인

1
음, 가정 set하고 check이 공유 변수에 경쟁을 피할 수 있기 때문에 아마,이 성능이 중요합니다 특히, 옵션 A와 함께 갈 것, 안전하게 병렬로 실행할 수 있습니다 y.
시인

1

첫 번째 예에서 y.load()0을 읽는다고 y.load()전에 발생 하는 것은 아닙니다 y.store(1).

그러나 seq_cst로드가 마지막 seq_cst 저장소의 값을 전체 순서로 반환하거나 이전에 발생하지 않은 일부 non-seq_cst 저장소의 값을 반환한다는 규칙 덕분에 단일 전체 순서에서 더 이른 것을 의미합니다. (이 경우에는 존재하지 않음). 따라서 전체 순서 y.store(1)보다 빠르면 1을 반환했을 것입니다.y.load()y.load()

단일 총 주문에주기가 없으므로 증거는 여전히 정확합니다.

이 솔루션은 어떻습니까?

std::atomic<int> x2{0},y{0};

void thread_a(){
  set();
  x2.store(1);
  if(!y.load()) foo();
}

void thread_b(){
  y.store(1);
  if(!x2.load()) bar();
}

OP의 문제는 "X"에 대한 제어 권한이 없다는 것입니다. 래퍼 매크로 또는 그 뒤에 있으며 seq-cst store / load가 아닐 수도 있습니다. 나는 그것을 더 잘 강조하기 위해 질문을 업데이트했습니다.
Peter Cordes

@PeterCordes 아이디어는 그가 통제 할 수있는 또 다른 "x"를 만드는 것이었다. 대답을 명확하게하기 위해 이름을 "x2"로 바꿉니다. 필자는 일부 요구 사항이 누락되었다고 확신하지만 foo () 및 bar ()가 모두 호출되지 않도록하는 것이 유일한 요구 사항이면이를 충족시킵니다.
Tomek Czajka

그렇다면 if(false) foo();OP는 다음 중 어느 것도 원하지 않는다고 생각합니다 .P 재미있는 점이지만 OP는 지정된 조건에 따라 조건부 호출을 원한다고 생각합니다!
Peter Cordes

1
@TomekCzajka 님, 안녕하세요. 새로운 솔루션을 제안 해 주셔서 감사합니다. 그것은 중요한 부작용을 생략하기 때문에 특정 경우에는 작동하지 않습니다 check()(실제로의 의미에 대한 내 질문에 대한 내 의견 참조 set,check,foo,bar). if(!x2.load()){ if(check())x2.store(0); else bar(); }대신 작동 할 수 있다고 생각합니다 .
qbolec

1

@mpoeter는 왜 옵션 A와 B가 안전한지 설명했습니다.

실제 구현에서 실제로 옵션 A는 std::atomic_thread_fence(std::memory_order_seq_cst)B가 아닌 스레드 A 에만 필요하다고 생각합니다 .

실제로 seq-cst 저장소에는 전체 메모리 장벽이 포함되어 있거나 AArch64에서는 적어도 나중에 획득 또는 seq_cst로드로 다시 정렬 할 수 없습니다 ( stlr순차 릴리스는 ldar캐시에서 읽기 전에 저장소 버퍼 에서 비워야합니다).

C ++-> asm 맵핑 은 원자 상점 또는 원자로드에 상점 버퍼를 비우는 비용을 선택할 수 있습니다. 실제 구현을위한 적절한 선택은 원자로드를 저렴하게 만드는 것이므로 seq_cst 상점에는 전체 장벽 (StoreLoad 포함)이 포함됩니다. seq_cst로드는 대부분의로드 획득과 동일합니다.

(그러나 POWER는 아닙니다. seq_cst는 모든 스레드가 순서에 동의 할 수 있어야하기 때문에 동일한 코어의 다른 SMT 스레드에서 저장소 전달을 중지하려면 무거운 무게 동기화 = 전체 장벽이 필요합니다. 모든 seq_cst ops. 다른 스레드에서 다른 위치에 대한 두 개의 원자 쓰기가 항상 다른 스레드에서 동일한 순서로 표시됩니까? )

(물론 안전을 공식적으로 보장 하기 위해, 우리는 획득 / 릴리스 세트 ()-> check ()를 seq_cst 동기화로 승격시키기 위해 둘 다 울타리가 필요합니다. 이완 검사는 다른 스레드의 POV에서 막대로 재정렬 될 수 있습니다.)


옵션 C의 실제 문제 동기화 할 수 있는 가상의 관찰자 y와 더미 작업에 달려 있다는 것 입니다. 따라서 장벽 기반 ISA에 대해 asm을 만들 때 컴파일러가 해당 순서를 유지해야합니다.

실제 ISA에서는 실제로 적용됩니다. 두 스레드 모두 전체 장벽 또는 이와 동등한 것을 포함하며 컴파일러는 원자를 최적화하지 않습니다. 그러나 "장벽 기반 ISA로 컴파일"은 ISO C ++ 표준의 일부가 아닙니다. 코 히어 런트 공유 캐시는 asm 추론에는 존재하지만 ISO C ++ 추론에는 존재하지 않는 가상의 관찰자입니다.

작업에 대한 옵션 C의 경우, 우리는 같은 순서 필요 dummy1.store(13);/ y.load()/ set();(스레드 B에서와 같이) 일부 ISO C를 위반 ++ 규칙을 .

이 명령문을 실행하는 스레드는 (시퀀스 이전 (Sequenced Before) 때문에) 먼저 실행되는 것처럼 동작해야합니다 set(). 괜찮습니다. 런타임 메모리 순서 및 / 또는 컴파일 시간 재정렬 작업이 여전히 가능합니다.

두 seq_cst 작전 d1=13y시퀀스 된 전 (프로그램 순서)과 일치한다. set()seq_cst가 아니기 때문에 seq_cst ops의 존재하는 글로벌 주문에 참여하지 않습니다.

스레드 B는 dummy1.store와-동기화하지 않습니다 그래서 어떻게-전에 요구 사항을 set을 기준으로 d1=13적용 , 그 할당이 해제 조작에도 불구하고.

가능한 다른 규칙 위반은 없습니다. setSequenced-Before 와 일치하는 데 필요한 것을 찾을 수 없습니다 d1=13.

"dummy1.store releases set ()"추론은 결함입니다. 이 순서는 동기화하는 실제 관찰자 또는 asm에만 적용됩니다. @mpoeter가 대답했듯이 seq_cst 총 주문의 존재는 관계 이전에 발생하거나 암시하지 않으며 seq_cst 외부에서 주문을 공식적으로 보장하는 유일한 방법입니다.

런타임에이 재정렬이 실제로 발생할 수있는 일관된 공유 캐시가있는 모든 종류의 "일반"CPU는 그럴듯 ​​해 보이지 않습니다. (그러나 컴파일러는 제거 할 수있는 경우 dummy1dummy2다음 명확하게 우리가 문제가있는 것, 그리고 그이 표준에 의해 허용 된 것 같아요.)

그러나 C ++ 메모리 모델은 저장 버퍼, 공유 코 히어 런트 캐시 또는 리트머스 허용 순서 변경에 대해 정의되어 있지 않기 때문에 C ++ 규칙에 따라 위생에 필요한 것은 공식적으로 필요하지 않습니다. 이것은 스레드 전용으로 판명되는 seq_cst 변수조차도 최적화 할 수 있도록하기위한 것입니다. (현재 컴파일러는 물론 원자 객체의 다른 최적화를하지 않습니다.)

한 스레드가 실제로 set()마지막으로 볼 수있는 구현이고 다른 스레드가 set()첫 번째 소리를 볼 수 없는 구현. POWER조차도 그렇게 할 수 없었습니다. seq_cst로드 및 저장에는 POWER에 대한 전체 장벽이 포함됩니다. (IRIW 재정렬은 여기에서 관련이있을 수 있다고 의견을 제시했습니다 .C ++의 acq / rel 규칙은이를 수용 할만큼 약하지만, 동기화 또는 기타 상황 이외의 상황에 대한 보증의 총 부족은 HW보다 훨씬 약합니다. )

C ++은 실제로 관찰자 없는 한 non-seq_cst에 대해서는 아무것도 보증하지 않으며 그 관찰자에 대해서만 보장 합니다. 하나가 없으면 우리는 Schroedinger의 고양이 영토에 있습니다. 아니면 숲에 두 그루의 나무가 떨어지면 한 나무가 다른 나무보다 먼저 쓰러 졌습니까? (큰 숲이라면 일반 상대성 이론은 관찰자에 달려 있으며 보편적 동시성 개념은 없다고 말합니다.)


@mpoeter는 컴파일러가 seq_cst 객체에서도 더미로드 및 저장 작업을 제거 할 수 있다고 제안했습니다.

나는 아무것도 아무것도 작업과 동기화 할 수 없음을 증명할 수있을 때 정확하다고 생각합니다. 예를 들어 dummy2, 함수를 이스케이프하지 않는 것을 볼 수있는 컴파일러는 해당 seq_cst로드를 제거 할 수 있습니다.

이것은 적어도 하나의 실제 결과를 초래합니다. AArch64를 컴파일하면 나중에 seq_cst 저장소가 나중에 완화 된 작업으로 실제로 재정렬 될 수 있습니다. 이는 seq_cst 저장소 +로드 전에 저장소 버퍼를로드 할 수 없었습니다. 나중에로드가 실행될 수 있습니다.

물론 현재 컴파일러는 ISO C ++에서 금지하지 않더라도 원자를 전혀 최적화하지 않습니다. 그것은 표준위원회 에서 해결되지 않은 문제 입니다.

C ++ 메모리 모델에는 암시 적 관찰자가 없거나 모든 스레드가 순서에 동의해야한다는 요구 사항이 있기 때문에 이것이 가능하다고 생각합니다. 코 히어 런트 캐시를 기반으로 일부 보장을 제공하지만 동시에 모든 스레드에 대한 가시성을 요구하지는 않습니다.


좋은 요약! 나는에 동의 연습 만이 서열-CST 울타리가 있었다 스레드 경우는 아마 충분할 것입니다. 그러나 C ++ 표준을 기반으로의 최신 값을 볼 필요가 보장 되지 않으므로set() 스레드 B에서도 여전히 펜스를 사용합니다. seq-cst 펜스가있는 편안한 상점은 어쨌든 seq-cst-store와 거의 동일한 코드를 생성한다고 가정합니다.
시인

@ mpoeter : p, 나는 공식적으로가 아니라 실제로 만 이야기하고있었습니다. 해당 섹션의 끝에 메모를 추가했습니다. 그리고 실제로 대부분의 ISA에서 seq_cst 상점은 보통 평범한 상점 (휴식) + 장벽이라고 생각합니다. 또는 아닙니다; POWER에서 seq-cst 상점은 상점 sync 전에 (무거운 중량) 를 수행 합니다. godbolt.org/z/mAr72P 그러나 seq-cst 하중은 양쪽에 약간의 장벽이 필요합니다.
Peter Cordes

1

ISO 표준에서 std :: mutex는 seq_cst가 아닌 획득 및 릴리스 순서 만 보장합니다.

그러나 seq_cst조작의 특성이 아니므 로 "seq_cst ordering"을 갖는 것은 없습니다.

seq_cst주어진 구현 std::atomic또는 대체 원자 클래스 의 모든 작업에 대한 보증 입니다. 따라서 귀하의 질문은 소리가 나지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.