memory_order
|
헤더 파일에 정의됨
<stdatomic.h>
|
||
|
enum
memory_order
{
|
(C11부터) | |
memory_order
는 원자적 연산을 중심으로 일반적인 비원자적 메모리 접근을 포함한 메모리 접근들이 어떻게 순서화되어야 하는지를 지정합니다. 다중 코어 시스템에서 아무런 제약이 없는 경우, 여러 스레드가 여러 변수에 동시에 읽기 및 쓰기를 수행할 때, 한 스레드는 다른 스레드가 값을 기록한 순서와 다른 순서로 값의 변화를 관찰할 수 있습니다. 실제로 변경 사항의 명백한 순서는 여러 독자 스레드 간에도 다를 수 있습니다. 메모리 모델이 허용하는 컴파일러 변환으로 인해 유니프로세서 시스템에서도 유사한 효과가 발생할 수 있습니다.
언어와 라이브러리의 모든 원자적 연산의 기본 동작은
language
순차적 일관성 순서
를 제공합니다(아래 논의 참조). 이 기본값은 성능에 영향을 미칠 수 있지만, 라이브러리의 원자적 연산에는 추가
memory_order
인수를 지정하여 원자성 이상의 정확한 제약 조건을 컴파일러와 프로세서가 해당 연산에 대해 강제하도록 할 수 있습니다.
목차 |
상수
|
헤더 파일에 정의됨
<stdatomic.h>
|
|
| 값 | 설명 |
memory_order_relaxed
|
완화된 연산: 다른 읽기나 쓰기 작업에 대한 동기화나 순서 제약을 부과하지 않으며, 이 연산의 원자성만 보장됨 (아래의 완화된 순서 지정 참조). |
memory_order_consume
(C++26에서 사용 중단됨) |
이 메모리 순서를 사용하는 로드 연산은 영향을 받는 메모리 위치에서 소비 연산 을 수행함: 현재 로드되는 값에 종속된 현재 스레드의 읽기나 쓰기 작업이 이 로드보다 앞서 재배열될 수 없음. 동일한 원자 변수를 해제하는 다른 스레드에서 데이터 종속 변수에 대한 쓰기 작업이 현재 스레드에서 가시적임. 대부분의 플랫폼에서 이는 컴파일러 최적화에만 영향을 미침 (아래의 해제-소비 순서 지정 참조). |
memory_order_acquire
|
이 메모리 순서를 사용하는 로드 연산은 영향을 받는 메모리 위치에서 획득 연산 을 수행함: 현재 스레드의 읽기나 쓰기 작업이 이 로드보다 앞서 재배열될 수 없음. 동일한 원자 변수를 해제하는 다른 스레드의 모든 쓰기 작업이 현재 스레드에서 가시적임 (아래의 해제-획득 순서 지정 참조). |
memory_order_release
|
이 메모리 순서를 사용하는 스토어 연산은 해제 연산 을 수행함: 현재 스레드의 읽기나 쓰기 작업이 이 스토어보다 뒤로 재배열될 수 없음. 현재 스레드의 모든 쓰기 작업이 동일한 원자 변수를 획득하는 다른 스레드에서 가시적이며 (아래의 해제-획득 순서 지정 참조), 원자 변수로 의존성을 전달하는 쓰기 작업이 동일한 원자 변수를 소비하는 다른 스레드에서 가시적이 됨 (아래의 해제-소비 순서 지정 참조). |
memory_order_acq_rel
|
이 메모리 순서를 사용하는 읽기-수정-쓰기 연산은 획득 연산 이자 해제 연산 임. 현재 스레드의 메모리 읽기나 쓰기 작업이 로드보다 앞서 재배열될 수 없으며, 스토어보다 뒤로도 재배열될 수 없음. 동일한 원자 변수를 해제하는 다른 스레드의 모든 쓰기 작업이 수정 전에 가시적이며, 수정 작업은 동일한 원자 변수를 획득하는 다른 스레드에서 가시적임. |
memory_order_seq_cst
|
이 메모리 순서를 사용하는 로드 연산은 획득 연산 을 수행하고, 스토어는 해제 연산 을 수행하며, 읽기-수정-쓰기 연산은 획득 연산 과 해제 연산 을 모두 수행함. 또한 모든 스레드가 동일한 순서로 모든 수정 사항을 관찰하는 단일 전체 순서가 존재함 (아래의 순차적 일관성 순서 지정 참조). |
|
이 섹션은 불완전합니다
Reason: happens-before 및 다른 개념들, C++에서와 같이, 하지만 수정 순서와 네 가지 일관성은 유지 c/language/atomic |
|
이 섹션은 불완전합니다
이유: 위 작업을 수행할 때, C11에서 게시된 대로 happens-before가 비순환이 아니었지만, DR 401을 통해 C++11과 일치하도록 업데이트되었음을 잊지 마십시오 |
릴렉스드 오더링
memory_order_relaxed 태그가 지정된 원자 연산은 동기화 연산이 아닙니다; 이들은 동시적 메모리 접근 간에 순서를 부과하지 않습니다. 오직 원자성과 수정 순서 일관성만을 보장합니다.
예를 들어, x 와 y 가 초기에 0인 경우,
// 스레드 1:
r1
=
atomic_load_explicit
(
y, memory_order_relaxed
)
;
// A
atomic_store_explicit
(
x, r1, memory_order_relaxed
)
;
// B
// 스레드 2:
r2
=
atomic_load_explicit
(
x, memory_order_relaxed
)
;
// C
atomic_store_explicit
(
y,
42
, memory_order_relaxed
)
;
// D
는
r1
==
r2
==
42
를 생성할 수 있습니다. 왜냐하면 스레드 1 내에서 A가 B보다
sequenced-before
관계에 있고, 스레드 2 내에서 C가 D보다
sequenced-before
관계에 있지만,
y
의 수정 순서에서 D가 A보다 먼저 나타나는 것을 막는 것은 없으며,
x
의 수정 순서에서 B가 C보다 먼저 나타나는 것을 막는 것도 없기 때문입니다.
y
에 대한 D의 부수 효과는 스레드 1의 로드 A에서 보일 수 있으며,
x
에 대한 B의 부수 효과는 스레드 2의 로드 C에서 보일 수 있습니다. 특히, 컴파일러 재배치나 런타임 중에 스레드 2에서 C보다 D가 먼저 완료되는 경우 이런 현상이 발생할 수 있습니다.
완화된 메모리 순서의 일반적인 사용 예는 참조 카운터 와 같은 카운터 증분입니다. 이는 원자성만 필요로 하고 순서나 동기화는 필요로 하지 않기 때문입니다.
릴리스-컨슘 순서 지정
스레드 A의 원자적 저장 연산이 memory_order_release 로 태그되고, 동일한 변수에 대한 스레드 B의 원자적 읽기 연산이 memory_order_consume 로 태그되며, 스레드 B의 읽기 연산이 스레드 A의 저장 연산이 기록한 값을 읽는 경우, 스레드 A의 저장 연산은 스레드 B의 읽기 연산에 대해 의존성 순서가 앞선다 .
스레드 A의 관점에서 원자적 저장 연산 이전에 happened-before 관계에 있는 모든 메모리 기록(비원자적 및 완화된 원자적)은, 스레드 B에서 로드 연산이 carries dependency 관계를 형성하는 연산들 내에서 visible side-effects 가 됩니다. 즉, 원자적 로드가 완료되면, 스레드 B에서 로드로부터 얻은 값을 사용하는 연산자와 함수들은 스레드 A가 메모리에 기록한 내용을 반드시 인지하게 됩니다.
동기화는 동일한 원자 변수를 해제하는 스레드와 소비하는 스레드 사이에서만 확립됩니다. 다른 스레드들은 동기화된 스레드 중 하나 또는 둘 모두와 다른 메모리 접근 순서를 관찰할 수 있습니다.
DEC Alpha를 제외한 모든 주류 CPU에서 종속성 순서 지정은 자동으로 이루어지며, 이 동기화 모드에 대해 추가 CPU 명령어가 발행되지 않고 특정 컴파일러 최적화만 영향을 받습니다(예: 컴파일러는 종속성 체인에 포함된 객체에 대해 추측적 로드를 수행하는 것이 금지됩니다).
이 순서의 일반적인 사용 사례는 드물게 쓰여지는 동시 데이터 구조(라우팅 테이블, 설정, 보안 정책, 방화벽 규칙 등)에 대한 읽기 접근과 포인터를 통한 발행-구독 상황, 즉 생산자가 포인터를 발행하고 소비자가 해당 포인터를 통해 정보에 접근할 수 있는 경우를 포함합니다: 약한 순서 아키텍처에서는 생산자가 메모리에 쓴 다른 모든 내용을 소비자에게 가시적으로 만들 필요가 없습니다(이는 비용이 큰 연산일 수 있습니다). 이러한 시나리오의 예시로는
rcu_dereference
가 있습니다.
현재(2015년 2월 기준) 알려진 상용 컴파일러들은 종속성 체인을 추적하지 않습니다: consume 연산은 acquire 연산으로 상승됩니다.
릴리스 시퀀스
어떤 원자 변수가 store-release되고 다른 여러 스레드들이 그 원자 변수에 대해 read-modify-write 연산을 수행하면 "릴리스 시퀀스"가 형성됩니다: 동일한 원자 변수에 대해 read-modify-write를 수행하는 모든 스레드들은
memory_order_release
의미론을 갖지 않더라도 첫 번째 스레드와 서로 동기화됩니다. 이를 통해 개별 소비자 스레드들 사이에 불필요한 동기화를 부과하지 않고도 단일 생산자 - 다중 소비자 상황이 가능해집니다.
릴리스-어퀴어 순서 지정
스레드 A의 원자적 저장 연산이 memory_order_release 로 태그되고, 동일한 변수에 대한 스레드 B의 원자적 로드 연산이 memory_order_acquire 로 태그되며, 스레드 B의 로드 연산이 스레드 A의 저장 연산이 기록한 값을 읽는 경우, 스레드 A의 저장 연산은 스레드 B의 로드 연산과 동기화(synchronizes-with) 관계를 형성합니다.
스레드 A의 관점에서 원자적 저장 이전에 happened-before 관계에 있는 모든 메모리 기록(비원자적 및 완화된 원자적 기록 포함)은 스레드 B에서 visible side-effects 가 됩니다. 즉, 원자적 로드가 완료되면 스레드 B는 스레드 A가 메모리에 기록한 모든 내용을 보게 됩니다. 이 약속은 B가 실제로 A가 저장한 값을 반환하거나 릴리스 시퀀스에서 이후의 값을 반환하는 경우에만 유효합니다.
동기화는 동일한 원자 변수를 해제하는 스레드와 획득하는 스레드 사이에서만 수립됩니다. 다른 스레드들은 동기화된 스레드 중 하나 또는 둘 모두와 다른 메모리 접근 순서를 관찰할 수 있습니다.
강력하게 정렬된 시스템(x86, SPARC TSO, IBM 메인프레임 등)에서는 대부분의 연산에 대해 릴리스-획득 순서가 자동으로 적용됩니다. 이 동기화 모드에는 추가적인 CPU 명령어가 발생하지 않으며, 특정 컴파일러 최적화만 영향을 받습니다(예: 컴파일러는 비원자적 저장을 원자적 저장-릴리스 이후로 이동시키거나 비원자적 로드를 원자적 로드-획득보다 앞서 수행하는 것이 금지됩니다). 약하게 정렬된 시스템(ARM, Itanium, PowerPC)에서는 특수 CPU 로드 또는 메모리 펜스 명령어가 사용됩니다.
상호 배제 잠금, 예를 들어 mutexes 또는 atomic spinlocks ,은 릴리스-획득 동기화의 예시입니다: 잠금이 스레드 A에 의해 해제되고 스레드 B에 의해 획득될 때, 임계 영역(해제 전)에서 스레드 A의 컨텍스트에서 발생한 모든 것은 동일한 임계 영역을 실행하는 스레드 B(획득 후)에게 가시적이어야 합니다.
순차적 일관성 순서
memory_order_seq_cst 태그가 지정된 원자적 연산들은 릴리스/어퀴어 순서와 동일한 방식으로 메모리를 정렬할 뿐만 아니라(한 스레드에서 저장 연산 이전에 발생한 모든 것 이 로드를 수행한 스레드에서 가시적 부수 효과 가 됨), 이렇게 태그된 모든 원자적 연산들의 단일 총 수정 순서 를 확립합니다.
공식적으로,
각
memory_order_seq_cst
연산 B가 원자 변수 M에서 로드할 때, 다음 중 하나를 관찰합니다:
- 단일 전체 순서에서 B 이전에 나타나는, M을 수정한 마지막 연산 A의 결과,
-
또는, 그러한 A가 존재했을 경우 B는 A보다
happen-before
관계에 있지 않으며
memory_order_seq_cst가 아닌 M에 대한 일부 수정 결과를 관찰할 수 있습니다, -
또는, 그러한 A가 존재하지 않았을 경우 B는
memory_order_seq_cst가 아닌 M에 대한 관련 없는 일부 수정 결과를 관찰할 수 있습니다.
만약
memory_order_seq_cst
atomic_thread_fence
연산 X가 B에
sequenced-before
관계에 있다면, B는 다음 중 하나를 관찰합니다:
-
단일 전체 순서에서 X 앞에 나타나는 M의 마지막
memory_order_seq_cst수정, - M의 수정 순서에서 나중에 나타나는 M의 관련 없는 수정.
M에 대한 원자 연산 쌍 A와 B가 있고, 여기서 A는 M의 값을 쓰고 B는 M의 값을 읽는 경우, 두 개의
memory_order_seq_cst
atomic_thread_fence
X와 Y가 존재하며, A가 X에 대해
sequenced-before
관계에 있고, Y가 B에 대해
sequenced-before
관계에 있으며, 단일 총 순서에서 X가 Y보다 앞서 나타나는 경우, B는 다음 중 하나를 관찰합니다:
- A의 효과,
- M의 수정 순서에서 A 이후에 나타나는 M의 관련 없는 일부 수정.
M에 대한 원자적 수정 쌍 A와 B에 대해, B가 M의 수정 순서에서 A 이후에 발생하는 경우
-
A가 X에 대해
sequenced-before
관계에 있고 X가 단일 전체 순서에서 B보다 앞에 나타나는
memory_order_seq_cstatomic_thread_fence X가 존재하거나, -
Y가 B에 대해
sequenced-before
관계에 있고 A가 단일 전체 순서에서 Y보다 앞에 나타나는
memory_order_seq_cstatomic_thread_fence Y가 존재하거나, -
A가 X에 대해
sequenced-before
관계에 있고 Y가 B에 대해
sequenced-before
관계에 있으며
X가 단일 전체 순서에서 Y보다 앞에 나타나는
memory_order_seq_cstatomic_thread_fence X와 Y가 존재합니다.
이것은 다음을 의미합니다:
memory_order_seq_cst
로 태그되지 않은 원자 연산이 등장하는 순간, 순차적 일관성은 사라집니다,
순차적 순서 지정은 모든 소비자가 모든 생산자의 동작이 동일한 순서로 발생하는 것을 관찰해야 하는 다중 생산자-다중 소비자 상황에서 필요할 수 있습니다.
전체 순차적 순서화는 모든 멀티 코어 시스템에서 완전한 메모리 펜스 CPU 명령어를 필요로 합니다. 이는 영향을 받는 메모리 접근들이 모든 코어로 전파되도록 강제하기 때문에 성능 병목 현상이 될 수 있습니다.
volatile과의 관계
실행 스레드 내에서, volatile lvalues 를 통한 접근(읽기 및 쓰기)은 동일 스레드 내 시퀀스 포인트로 구분된 관찰 가능한 부작용(다른 volatile 접근 포함)을 지나 재배열될 수 없습니다. 그러나 volatile 접근은 스레드 간 동기화를 설정하지 않기 때문에 이 순서가 다른 스레드에서 관찰된다는 보장은 없습니다.
또한, volatile 접근은 원자적이지 않으며 (동시 읽기 및 쓰기는 데이터 레이스 입니다) 메모리 순서를 정하지 않습니다 (비-volatile 메모리 접근들은 volatile 접근 주변에서 자유롭게 재정렬될 수 있습니다).
주목할 만한 예외는 Visual Studio로, 기본 설정에서는 모든 volatile 쓰기가 릴리스 의미를 가지고 모든 volatile 읽기가 획득 의미를 가집니다 (
Microsoft Docs
). 따라서 volatile 변수는 스레드 간 동기화에 사용될 수 있습니다. 표준
volatile
의미론은 다중 스레드 프로그래밍에 적용할 수 없지만, 동일한 스레드에서 실행되는
signal
핸들러와의 통신에는 충분합니다. 특히
sig_atomic_t
변수에 적용될 때 그러합니다. 컴파일러 옵션
/volatile:iso
를 사용하면 표준과 일관된 동작으로 복원할 수 있으며, 이는 대상 플랫폼이 ARM일 때 기본 설정입니다.
예제
|
이 섹션은 불완전합니다
이유: 예제 없음 |
참고문헌
- C23 표준 (ISO/IEC 9899:2024):
-
- 7.17.1/4 memory_order (p: TBD)
-
- 7.17.3 Order and consistency (p: TBD)
- C17 표준 (ISO/IEC 9899:2018):
-
- 7.17.1/4 memory_order (p: 200)
-
- 7.17.3 Order and consistency (p: 201-203)
- C11 표준 (ISO/IEC 9899:2011):
-
- 7.17.1/4 memory_order (p: 273)
-
- 7.17.3 Order and consistency (p: 275-277)
참고 항목
|
C++ documentation
for
memory order
|
외부 링크
| 1. | MOESI 프로토콜 |
| 2. | x86-TSO: x86 멀티프로세서를 위한 엄격하고 실용적인 프로그래머 모델 P. Sewell 외, 2010 |
| 3. | ARM 및 POWER 완화 메모리 모델에 대한 튜토리얼 소개 P. Sewell 외, 2012 |
| 4. | MESIF: 점대점 상호 연결을 위한 2홉 캐시 일관성 프로토콜 J.R. Goodman, H.H.J. Hum, 2009 |
| 5. | 메모리 모델 Russ Cox, 2021 |
|
이 섹션은 불완전합니다
이유: QPI, MOESI, 그리고 Dragon에 대한 좋은 참고 자료를 찾아봅시다. |