c11이랑 c++11 표준에 atomic이 추가되었는데, memory_order라던가에 대한 한글 자료가 별로 없어서 정리해본다.


일하다 집중안되면 끄적끄적할거라 언제까지 할지도 모르겠지만 아는데까지 정리해볼게. 혹시 틀린 내용이 있거든 알려주면 바로 수정할게.


memory ordering부터 시작해볼까 하는데, 다음과 같은 메모리 공간에 A와 B가 있고 A와 B의 초기값은 0이라고 생각해보자.


어떤 관측자가 있어서 이 메모리를 관측하는데, A를 관측한 에, B를 관측한다고 치자.


─┬─┬─

│A=0│B=0

Observer : A를 관측한 에, B를 관측


다음과 같은 C 코드가 컴파일되어 수행되면 Observer가 관측 가능한 (A, B)는 어떻게 될까?


A = 1;

B = 2;


보통은 (0, 0), (1, 0), (1, 2) 세가지 경우라고 생각할 거야.


그런데 실제로는 (0, 2)가 관측될 수도 있어. 소스코드에는 분명 A에다 1을 먼저 쓰고 B에다 2를 쓰도록 했는데, A에 1이 써지기 전에 B에 2가 써진 거지.


이건 컴파일러와 CPU가 모두 리오더링을 할 가능성이 있기 때문인데, 일단 컴파일러가 리오더링하는 경우를 생각해보자.


컴파일러가 기계어 코드를 생성하는데, 이 때 컴파일러가 다음과 같이 순서를 맘대로 바꿀 수가 있어.


STORE A, 1

STORE B, 2


또는 순서를 바꿔서


STORE B, 2

STORE A, 1


컴파일러가 순서를 맘대로 바꿨을 경우에는 소스코드 상으로는 A에다 1을 먼저 쓰지만 실제로는 B에 2를 쓰는 기계어가 A에 1을 쓰는 기계어 앞에 나올 수 있는거지.


운이 좋아서 컴파일러가 A와 B를 순서대로 쓰는 코드를 생성했다고 하더라도, CPU에서도 리오더링이 일어날 수 있어. 이건 무슨 소리냐면,

─┬─┬─

│A=0│B=0

─ ↑ CPU0 Observer (STORE A, 1) A를 관측한 에, B를 관측 (STORE B, 2)


이 경우에, A와 B가 다른 영역이잖아? 그럼 CPU가 메모리에 write을 할 때 이 순서를 바꿔버릴 수가 있어.


분명 instruction 순서는 A에 1을 쓴 후에 B에 2를 쓰는 순서인데, CPU가 순서를 바꿔서 B에다 2를 쓰고난 후 A에다 1을 쓸 수도 있다는 거지.


이러한 리오더링을 막기 위해서는 컴파일러와 CPU의 리오더링을 모두 막을 수 있는 방법이 필요한데, c11/c++11 이전에는 표준적인 방법이 없었어. (물론 방법이 전혀 없었던 것은 아냐. 인라인 어셈블리를 이용해서 컴파일러 리오더링을 막고 사용하는 CPU에 맞는 배리어 instruction을 삽입해야하는데, 물론 비표준 코드다 보니 컴파일러마다 CPU마다 필요한 코드를 제각각 사용할 수 밖에 없었지.)


c11/c++11에 와서는 atomic과 함께 memory_order가 추가되었는데, 앞의 경우에서처럼 A에다 1을 쓴 에 B에다 2를 쓴 것이 관측되도록 하기 위해서는 release semantic이라는 것이 사용돼.


std::atomic을 사용해서 표현하자면 복잡해지니까 그냥 memory order만 표기하도록 할게.


A = 1;

B = 2, memory_order_release;


이와 같이 B에 2를 저장하는 operation에 release를 태그하면, 컴파일러가 (A, B) = (0, 2)가 관측될 수 없도록 기계어를 생성해. 물론 CPU 리오더링을 막기 위해 필요한 barrier instruction을 넣어서.


실제로 arm gcc를 통해서 확인해보자. 다음과 같은 코드를 컴파일해보면


#include <atomic>


int a = 0;

std::atomic<int> b(0);


void f()

{

  a = 1;

  b.store(2, std::memory_order_release);

}


생성된 코드는 다음과 같아.


movs r2, #1

str  r2, [A]

movs r2, #2

dmb  sy      // 메모리 배리어 인스트럭션

str  r2, [B]


A에 1을 쓰고 나서 필요한 인스트럭션을 삽입한 후에 B에다 2를 쓰는 걸 확인할 수 있을거야.


release semantic을 쓰면 이처럼 외부에서 관측되는 메모리의 순서를 지정할 수 있어.


지금 설명할 때 Observer가 (A를 관측한 에 B를 관측한다)고 설명하면서 를 강조했는데, 다음에는 관측자의 입장에서 필요한 내용을 정리해볼게.


좋은 하루 되길 바라~