1.Mutex, Semaphore 의 동작 원리는?

락들의 동작내부는 대부분 똑같다.

Untitled

msvc의 std::mutex를 뜯어보면 안엔 lock bts (bit test and set) 이 있다. atomic operation은 x86에선 명령어 앞에 이렇게 lock 접두어가 붙는다. 결국 어떤 락이든 뜯어보면 내부는 atomic operation이다. 하드웨어의 특수한 지원이 필요하다. (대부분)

swap(&a, &b)  // xchg. (lock exchange)
i++           // lock inc
a += b.       // lock xadd (add and exchange)

atomic operation이란?

mutex, semaphore는 어떤 코드영역 에 대해 락을 걸지만 atomic operation은 한 변수에 대해서만 작게 락을 건다. 당연히 mutex같은 락보다 훨씬 빠르다.

#include <stdatomic.h>

atomic_int a; //< a에 적용되는 모든 연산은 원자적임(락상태)

// 아래 연산은 모두 atomic하다. (별다른 락을 걸지 않아도 됨)
a++;
a += 5;
a = b;

2. 애초에 Race Condition 문제가 왜 발생하는가?

Cache Coherence가 보장되는 cpu에서 atomic operation이 왜 필요할까. 하나의 operation은 여러 단계로 쪼개어질 수 있다. 예를 들어 i++ 이라는 코드는 다음과 같은 uop으로 쪼개어질 수 있다.

// i++
load  eax, [i]
add.  eax, 1
store [i], eax

캐시 일관성 프로토콜은 저기서 “store를 수행할 때” 만 다른 코어들의 캐시버스를 잠근다. 그래서 다른 코어의 load 실행을 막지 못하기 때문에 Race condition 이 존재한다.

캐시 일관성(Cache Coherence)(MESI Protocol) 란?

Untitled

CPU의 코어0의 L1캐시와 코어1의 L1캐시는 독립적이다.

한 프로그램을 멀티스레딩으로 돌린다고 생각해보자. 쓰기스레드는 a = 1 를 하고, 읽기스레드는 a를 관측한다.

이 두 스레드가 별개의 코어에서 실행된다면, 쓰기스레드가 a = 1 를 해봤자, 쓰기스레드의 로컬캐시에만 쓰기가 되기 때문에 읽기스레드는 평생 a == 1 를 관측할 수 없다.

그래서 로컬캐시 내용이 수정되면 다른 코어도 수정된 최신 내용을 업데이트 받아올 수 있도록 하는 기능이 캐시 일관성이다. 우리가 사용하는 cpu 는 대부분 이 기능을 지원한다.

동작원리- 한 코어가 쓰기를 수행하면 다른 코어들의 해당 데이터를 무효화시켜버린다. (store가 수행되는 동안 다른 코어들이 해당 데이터에 접근하지 못하도록 버스를 잠근다) 그러면 다른 코어들이 들고 있는 데이터는 무효화 된 데이터 이므로, 해당 데이터를 사용하려면 최신 데이터를 들고있는 코어에게서 캐시간 전송으로 받아온다.

Untitled