CPU 와 컴퓨터 메모리인 RAM 은 물리적으로 떨어져 있음. 따라서 CPU 가 메모리에서 데이터를 읽어 오기 위해서는 꽤 많은 시간이 걸림.
> 이를 보완하기 위해 캐시(Cache) 등장!
=Cache hit),=Cache miss)으로 진행여기서 말하는 특정한 방식은 CPU마다 다른데, 대표적인 예로 LRU (Least Recently Used) 가 있음.
즉, 가장 이전에 쓴 캐시를 날려버리고 그 자리에 새로운 캐시를 기록하는 것.
-> 최근에 접근한 데이터를 자주 반복해서 접근한다면 매우 유리
CPU에서 명령어를 실행할 때 여러 task 단계를 거침.
fetch : 명령어를 읽어야 하고, decode : 읽은 명령어가 무엇인지 해석해야 하고,execute : 해석된 명령어를 실행하고,write : 마지막으로 결과를 작성파이프라이닝
한 작업이 끝나기 전에, 다음 작업을 시작하는 방식으로 동시에 여러 개의 작업을 동시에 실행하는 것
하지만 각 tast의 실행 속도가 달라 비효율적일 수 있음.
: ex) 세탁이나 빨래 개기는 30 분 밖에 안걸리는데 건조가 3시간이 걸린다면, 건조기 기다리느라 빨래를 계속 할 수 없음
따라서, 컴파일러는 우리가 어떠한 최대한 CPU 의 파이프라인을 효율적으로 활용할 수 있도록 명령어를 재배치하게 됨.
수정 순서
만약에 어떤 객체의 값을 실시간으로 확인할 수 있는 전지전능한 무언가가 있다고 하였을 때, 해당 객체의 값의 변화를 기록한 것
C++ 에서, 원자적 연산을 할 경우에, 모든 쓰레드에서 같은 객체에 대해서 동일한 수정 순서 를 관찰할 수 있음을 보장.

어떤 쓰레드가 a 의 값을 읽었을 때, 8 로 읽었다면,
그 다음에 읽어지는 a 의 값은 반드시 8, 6, 3 중에 하나여야 함.
수정 순서를 거꾸로 거슬러 올라가서 5 를 읽는 일은 없음.
=> 이처럼 모든 쓰레드에서 변수의 수정 순서에 동의만 한다면, 모든 쓰레드들이 동일한 값을 관찰할 필요는 없음
C++ 에서 모든 쓰레드들이 수정 순서에 동의를 해야만 하는 경우는,
바로 모든 연산들이 원자적 일 때
원자적(atomic)
CPU 가 명령어 1 개로 처리하는 명령으로, 중간에 다른 쓰레드가 끼어들 여지가 전혀 없는 연산
(즉, 이 연산을 반 정도 했다 는 있을 수 없고 이 연산을 했다 혹은 안 했다 만 존재할 수 있음)
std::atomic<int> counter(0);
코드에서는, atomic 의 템플릿 인자로 원자적으로 만들고 싶은 타입을 전달하면됨.
위 경우 0 으로 초기화 된 원자적인 변수를 정의함.
atomic 객체들의 경우 원자적 연산 시에 메모리에 접근할 때 어떠한 방식으로 접근하는지 지정할 수 있음.
memory_order_relaxed 방식으로 메모리에서 읽거나 쓸 경우, 주위의 다른 메모리 접근들과 순서가 바뀌어도 무방함.#include <atomic>
#include <cstdio>
#include <thread>
#include <vector>
using std::memory_order_relaxed;
void t1(std::atomic<int>* a, std::atomic<int>* b) {
b->store(1, memory_order_relaxed); // b = 1 (쓰기)
int x = a->load(memory_order_relaxed); // x = a (읽기)
printf("x : %d \n", x);
}
void t2(std::atomic<int>* a, std::atomic<int>* b) {
a->store(1, memory_order_relaxed); // a = 1 (쓰기)
int y = b->load(memory_order_relaxed); // y = b (읽기)
printf("y : %d \n", y);
}
int main() {
std::vector<std::thread> threads;
std::atomic<int> a(0);
std::atomic<int> b(0);
threads.push_back(std::thread(t1, &a, &b));
threads.push_back(std::thread(t2, &a, &b));
for (int i = 0; i < 2; i++) {
threads[i].join();
}
}

실행 시 위와 같은 결과 나옴.
혹은 x:1, y:0 or y:1, x:1과 같은 결과 나올 수도 있음.
b->store(1, memory_order_relaxed); // b = 1 (쓰기)
int x = a->load(memory_order_relaxed); // x = a (읽기)
store 과 load 는 atomic 객체들에 대해서 원자적으로 쓰기와 읽기를 지원해주는 함수임.
이 때, 추가적인 인자로 어떤 형태로 memory_order을 지정할 것인지 전달할 수 있는데 위의 경우 memory_order_relaxed 전달.
따라서 메모리 연산은 CPU 마음대로 아래와 같이 재배치할 수 있음.
int x = a->load(memory_order_relaxed); // x = a (읽기)
b->store(1, memory_order_relaxed); // b = 1 (쓰기)
data[0].store(1, memory_order_relaxed);
data[1].store(2, memory_order_relaxed);
data[2].store(3, memory_order_relaxed);
is_ready.store(true, std::memory_order_release);
memory_order_release
data 의 원소들을 store 하는 명령들은 모두 relaxed 때문에 자기들 끼리는 CPU 에서 마음대로 재배치될 수 있겠지만!!!! 아래 release 명령을 넘어가서 재배치될 수 는 없음while (!is_ready->load(std::memory_order_acquire)) {
}
memory_order_acquire
두 개의 다른 쓰레드들이 같은 변수의
release와acquire를 통해서동기화 (synchronize)를 수행
acquire 와 release 를 모두 수행fetch_add 와 같은 함수에서 사용될 수 있음atomic 객체를 사용할 때, memory_order 를 지정해주지 않는다면 디폴트로 memory_order_seq_cst 가 지정순차적 일관성
메모리 명령 재배치도 없고, 모든 쓰레드에서 모든 시점에 동일한 값을 관찰할 수 있는, 우리가 생각하는 그대로 CPU 가 작동하는 방식
