일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- boj 6443
- boj 22942
- 홀짝트리
- 데이터 체커
- lock based stack
- lock free stack
- pcce 기출문제 풀이
- PCCE
- DirectX
- c++
- boj 15724
- 2025 프로그래머스 코딩챌린지 1차예선
- 색종이와가위
- boj 1074
- boj 1958
- 프로그래밍공부
- 지게차와 크레인
- boj 20207
- 비밀 코드 해독
- pccp 기출문제 풀이
- tessellation
- lock based queue
- orthographic projection
- render target
- 브루트포스
- DirectX12
- LCS
- boj 11053
- dp
- boj 21921
- Today
- Total
오구의코딩모험
[Server] 메모리 모델, Thread Local Storage (= TLS), Lock-Based Stack/Queue 본문
[Server] 메모리 모델, Thread Local Storage (= TLS), Lock-Based Stack/Queue
오구.cpp 2025. 3. 31. 15:40메모리 모델, TLS, 락 기반 자료구조 학습 정리
이번엔 C++ 멀티스레드 프로그래밍에서 중요한 개념들인 Memory Model, Thread Local Storage, atomic, 그리고 Lock-Based 자료구조(스택/큐)를 공부했습니다.
1. 메모리 모델 (Memory Order)
Memory Model (정책)
1) Sequentially Consistent (seq_cst)
2) Acquire-Release (acquire, release)
3) Relaxed (relaxed)
(1) seq_cst (가장 엄격 = 컴파일러 최적화 여지 적음 = 직관적)
- 가시성 문제 바로 해결! 코드 재배치 바로 해결!
(2) acquire-release
- 딱 중간!
- release 명령 이전의 메모리 명령들이, 해당 명령 이후로 재배치 되는 것을 금지
- 그리고 acquire로 같은 변수를 읽는 쓰레드가 있다면
- release 이전의 명령들이 -> acquire 하는 순간에 관찰 가능 (가시성 보장)
(3) relaxed (자유롭다 = 컴파일러 최적화 여지 많음 = 직관적이지 않음)
- 코드 재배치도 멋대로 가능! 가시성 해결 NO!
- 가장 기본 조건 (동일 객체에 대한 동일 관전 순서만 보장)
* 인텔, AMD의 경우 애당초 순차적 일관성을 보장을 해서 seq_cst를 써도 별다른 부하가 없음
* ARM의 경우 꽤 차이가 있다고 한다!
memory_order 종류 요약
- seq_cst : 가장 강력한 일관성 보장, 코드 재배치 없음, 컴파일러 최적화 거의 못함 (직관적)
- acquire/release : 적절한 수준의 최적화 허용, 필요한 시점만 제한
- relaxed : 최대의 성능, 그러나 동기화 보장 없음 (복잡한 상황에 적합)
예시 코드
atomic<bool> ready;
int32 value;
void Producer()
{
value = 10;
ready.store(true, memory_order::release); // 이후 명령 재배치 방지
}
void Consumer()
{
while (ready.load(memory_order::acquire) == false)
;
// 여기 도달했을 땐 value = 10 이 보장됨
}
2. atomic 연산자들
기본적인 사용 예시
atomic<bool> flag = false;
// 값을 교환
bool old = flag.exchange(true);
// 조건부 교체 (Compare-And-Swap)
bool expected = false;
bool desired = true;
bool success = flag.compare_exchange_strong(expected, desired);
- exchange() : 값 바꾸고 이전 값 리턴
- compare_exchange_strong() : 조건 맞으면 값 교체, 실패 시 expected가 최신 값으로 갱신됨
3. Thread Local Storage (TLS)
- thread_local을 사용하면 스레드마다 독립적인 전역 변수를 가질 수 있다.
- 스레드 ID나 로컬 상태 저장 등에 유용
예시 코드
thread_local int32 LThreadId = 0;
void ThreadMain(int32 threadId)
{
LThreadId = threadId;
while (true)
{
cout << "Hi I am Thread " << LThreadId << endl;
this_thread::sleep_for(1s);
}
}
int main()
{
vector<thread> threads;
for (int32 i = 0; i < 10; ++i)
threads.push_back(ThreadMain, i + 1);
for (thread& t : threads)
t.join();
}
4. Lock-Based Stack / Queue
<Stack>
template<typename T>
class LockStack
{
public:
void Push(T value)
{
lock_guard<mutex> lock(_mutex);
_stack.push(std::move(value));
_condVar.notify_one();
}
bool TryPop(T& value)
{
lock_guard<mutex> lock(_mutex);
if (_stack.empty()) return false;
value = std::move(_stack.top());
_stack.pop();
return true;
}
void WaitPop(T& value)
{
unique_lock<mutex> lock(_mutex);
_condVar.wait(lock, [this] { return !_stack.empty(); });
value = std::move(_stack.top());
_stack.pop();
}
private:
stack<T> _stack;
mutex _mutex;
condition_variable _condVar;
};
<Queue>
template<typename T>
class LockQueue
{
public:
void Push(T value)
{
lock_guard<mutex> lock(_mutex);
_queue.push(std::move(value));
_condVar.notify_one();
}
bool TryPop(T& value)
{
lock_guard<mutex> lock(_mutex);
if (_queue.empty()) return false;
value = std::move(_queue.front());
_queue.pop();
return true;
}
void WaitPop(T& value)
{
unique_lock<mutex> lock(_mutex);
_condVar.wait(lock, [this] { return !_queue.empty(); });
value = std::move(_queue.front());
_queue.pop();
}
private:
queue<T> _queue;
mutex _mutex;
condition_variable _condVar;
};
<활용 예제>
LockQueue<int32> q;
void Push()
{
while (true)
{
int32 value = rand() % 100;
q.Push(value);
this_thread::sleep_for(10ms);
}
}
void Pop()
{
while (true)
{
int32 data = 0;
if (q.TryPop(OUT data))
cout << data << endl;
}
}
int main()
{
thread t1(Push);
thread t2(Pop);
thread t3(Pop);
t1.join();
t2.join();
t3.join();
}
- WaitPop 함수는 좀 더 유연한 잠금과 해제가 필요하다. (값이 들어왔을 때 해제, 값이 들어올 때까지 대기)
- 따라서 WaitPop은 unique_lock을 사용한다.
- Push / TryPop 함수는 스코프를 벗어나면 자동으로 뮤텍스를 해제해야 하므로 lock_guard를 사용한다.
정리

memory_order
- 대개 seq_cst를 사용하는 것이 일반적이다.
- release / acquire 조합을 잘 써도 퍼포먼스와 안정성 둘 다 챙길 수 있다.
atomic 연산
- exchange, compare_exchange_*는 thread-safe한 값 교체에 매우 유용
TLS
- 전역변수를 스레드별로 분리할 수 있다. thread_local 한 줄이면 끝!
LockStack / LockQueue
- TryPop은 논블로킹, WaitPop은 대기 방식 → 상황에 맞게 사용
condition_variable
- 반드시 조건을 람다로 넣고 while로 감싸자 (spurious wakeup 방지)
'Game > Server' 카테고리의 다른 글
[Server] Lock Free Stack (上편) (0) | 2025.04.02 |
---|---|
[Server] Condition Variable, Future (0) | 2025.03.28 |
[Server] SpinLock, Sleep, Event (0) | 2025.03.17 |
[Server] Game Server, Multi Thread, Atomic, Lock, Dead Lock (0) | 2025.01.16 |