예전에 스레드를 공부하다가 Lockfree라는 것을 알게 되었었고 일단 뭐든 만들어보자는 마인드로 MPMC부터 무턱대고 만들었었는데, 지금 와서 생각해 보니 남는 것도 없고 너무 얄팍하게 안 것 같아서 차근차근 개념을 이해하기 위해 작성한 글입니다.. 😎
🔎 Race condition
“경쟁은 왜 발생하는 것일까?” “경쟁 상태에 데이터를 읽고 쓰면 무슨 일이 벌어질까?“에 대한 명확한 이해가 먼저 필요했습니다. 상황별로 살펴보도록 하겠습니다.
Case 1: Read - Read
어떤 한 데이터를 2개 또는 그 이상 되는 스레드가 모두 읽기만 한다면 아무 일도 일어나지 않습니다. 락을 걸지 않아도 말이죠. 👍
Case 2: Write - Read (Dirty read)
이제부터 문제가 발생합니다. 스레드가 읽기만 하는 와중에 한 스레드가 갑자기 데이터를 변경해 버립니다. 이 때 만약 long(4byte) 타입의 데이터를 2byte를 읽고, 2byte를 읽는 도중에 써버린다면 불완전한 데이터를 읽을 수 있습니다.
CPU는 기본적으로 한 번의 버스 하드웨어 명령(Word 단위, 보통 64비트 시스템에서는 8바이트)으로 처리할 수 있는 크기를 넘어서거나, 메모리가 데이터 경계선에 딱 맞춰 정렬(Alignment)되어 있지 않으면 쓰기/읽기 작업을 여러 단계의 기계어 명령으로 분할해서 처리합니다.
Case 3: Write - Write (Lost update)
여러 스레드가 동시에(아주 짧은 시간에 순차적으로) 데이터를 쓴다면 결국 제일 마지막에 넣은 데이터만 남아있을 것입니다. 그 전에 넣었던 데이터는 모두 잃어버리게 되죠. 😭

2개 이상의 스레드들이 읽기만 한다면 문제가 없지만 쓰기를 하는 순간부터 별도의 락을 하지 않는 이상 프로그램이 어떤 동작을 할지 코드로 보장할 수 없고 예측 불가능한 상태인 Undefined behavior(미정의 동작)이 일어납니다.
SPSC Queue
생산자와 소비자, 두 개의 스레드에서 _top과 _bottom 두 변수를 읽고 쓰기 때문에 경쟁 상태가 맞습니다. 따라서 락을 사용하던, 원자설 변수를 통해 처리해야 합니다.
하지만 _top 변수는 생산자에서, _bottom은 소비자에서만 쓰기를 하므로 CAS 연산은 필요가 없습니다.
💡 명령어 재배치 방지를위한
std::memory_order는 사용합니다.