| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
Tags
- Lock-free Stack
- localstorage
- Behavior Design Pattern
- Github
- trie
- DP
- 분리 집합
- map
- 트라이
- 자바스크립트
- ccw 알고리즘
- Prisma
- 강한 연결 요소
- JavaScript
- 2-SAT
- LCA
- 최소 공통 조상
- 비트마스킹
- 벨만-포드
- 그래프 탐색
- Binary Lifting
- Spin Lock
- PROJECT
- 이분 탐색
- SCC
- Strongly Connected Component
- 비트필드를 이용한 dp
- 게임 서버 아키텍처
- R 그래프
- Express.js
Archives
- Today
- Total
dh_0e
[C++/Game Server] Lock-Free Stack (with PopCount, PendingList) 본문
내일배움캠프/Game Server
[C++/Game Server] Lock-Free Stack (with PopCount, PendingList)
dh_0e 2026. 2. 6. 18:40Lock-Free Stack with PopCount
- Lock 없이 스택 구현
- lock-free 라도 완전히 대기를 안 할 순 없음 (Atomic 변수, CAS 함수 사용)
- DeadLock이 아닌 LiveLock 비슷한 상황이 발생할 수 있음
- 데드락 (Deadlock): 모두가 잠들어서 아무도 움직이지 않음. (진행도 0, CPU 0%)
- 라이브락 (Livelock): 모두가 미친 듯이 제자리걸음을 하고 있음. (진행도 0, CPU 100%)
- 사실상 Lock-Free를 사용했기 때문에 LiveLock이 일어날 순 없지만 각각의 스레드 입장에서 Starvation이 심해지면 LiveLock처럼 느껴질 수 있음
- DeadLock이 아닌 LiveLock 비슷한 상황이 발생할 수 있음
template<typename T>
class LockFreeStack
{
struct Node {
Node(const T& value) : data(value), next(nullptr) {}
T data;
Node* next;
};
public:
void Push(const T& value)
{
Node* node = new Node(value);
node->next = _head;
while (_head.compare_exchange_weak(node->next, node) == false)
{
//node->next = _head; // 함수 안에서 자동으로 실행됨 (이 값이 바뀌었으니까 다시 알려줄게)
}
}
// 1) head 읽기
// 2) head->next 읽기
// 3) head = head->next
// 4) data 추출해서 반환
// 5) 추출한 노드를 삭제
bool TryPop(T& value)
{
++_popCount;
Node* oldHead = _head;
while (oldHead && _head.compare_exchange_weak(oldHead, oldHead->next) == false)
{
// oldHead=_head;
}
if (oldHead == nullptr) {
--_popCount;
return false;
}
value = oldHead->data;
TryDelete(oldHead);
return true;
}
// 1) 데이터 분리
// 2) Count 체크
// 3) 나 혼자면 삭제
void TryDelete(Node* oldHead)
{
// 나 외에 누가 있는지
if (_popCount == 1)
{
// 나 혼자임
// 이왕 혼자인거, 삭제 예약된 다른 데이터들도 삭제
Node* node = _pendingList.exchange(nullptr);
if (--_popCount == 0) // ********************************
{
// if문 이후로 끼어든 애가 없음 >> 삭제 진행
// 이제와서 끼어들어도, 데이터는 이미 분리해둔 상태
DeleteNodes(node);
}
else if(node)
{
// 누가 끼어들었으니 다시 갖다 놓기
ChainPendingNodeList(node);
}
// 내 데이터는 삭제
delete oldHead;
}
else
{
// 누가 있네
// 지금 당장 삭제하지 않고 pendingList에 넣어주기
ChainPendingNode(oldHead);
--_popCount;
}
}
// nullptr을 넣어놨지만, 그새 다른 스레드가 pendingList에 node를 추가했을 수 있음
// 내 스레드가 가지고 있는 pendingList 뒤에 넣어주고 CAS로 합친 List를 pendingList로 교체
void ChainPendingNodeList(Node* first, Node* last)
{
last->next = _pendingList;
while(_pendingList.compare_exchange_weak(last->next, first)==false)
{
//last->next = _pendingList;
}
}
// pendingList의 first와 last를 구분하여 ChainPendingNodeList로 보내줌
void ChainPendingNodeList(Node* node)
{
Node* last = node;
while (last->next)
{
last = last->next;
}
ChainPendingNodeList(node, last);
}
// 기존 pendingList의 head로 node를 넣어주기 위함
void ChainPendingNode(Node* node)
{
ChainPendingNodeList(node, node);
}
// pendingList를 탐색하며 delete 진행
static void DeleteNodes(Node* node)
{
while (node)
{
Node* next = node->next;
delete node;
node = next;
}
}
private:
atomic<Node*> _head;
atomic<uint32> _popCount = 0; // Pop을 실행중인 쓰레드 개수
atomic<Node*> _pendingList; // 삭제 되어야 할 노드들의 head (첫번째 노드)
};
"if(--_popCount == 0)" 과연 필요할까?
- (// **********************)로 체크한 부분
- 구조적으로 다른 스레드가 Pop에 접근한다고 해도 본 스레드가 _popCount를 증가시켜 놓은 상태이기 때문에 _pendingList에 있는 node들에 접근하지 못함
- if(--_popCount==0)이 없더라도 다른 스레드들이 들어와서 pop을 한다고 해도 UAF 없이 delete가 가능한 거 아닌가?

- 재배치/가시성 문제로 Node* oldHead = _head 가 _popCount++ 보다 먼저 실행될 때, delete로 인해 UAF가 발생할 수 있다는 말

- 하지만, 현재 _popCount를 atomic 변수로 선언했으며 seq_cst(기본)라서 컴파일러가 마음대로 재배치할 수 없으므로 재배치/가시성 문제가 발생할 수 없음
- memory_order_seq_cst가 아닌 memory_order_relaxed로 바뀐다면 재배치로 인해 문제가 발생할 수 있음


- 결국 중복되지만 Pop중인 스레드가 나밖에 없을 때만 free(delete) 하기 위한 안전 조건이라는 것
- 나중에 코드 최적화등을 대비한 것이지 없어도 된다는 뜻
- 그렇다면 괜히 다른 스레드가 pop을 한다는 사실만으로 DeleteNodes를 진행하지 않고, ChainPendingNodeList(node)를 해서 성능을 낭비하는 건 아닌지?

- 경쟁이 심할수록 delete의 비용이 커지기 때문에, 성능은 별 차이가 없다고 함
- 그렇다면 pendingList에 모아놨다가 스레드 경쟁이 거의 없을 때 한 번에 delete 하는 로직이 효율적이지 않나?

- 대체로 그렇지만 때에 따라 다르다고 함
- 와 thread 재밌다 스트레스 많이 받는다 도움 많이 될 거야
'내일배움캠프 > Game Server' 카테고리의 다른 글
| [Server] 게임 서버 아키텍처 (0) | 2024.08.06 |
|---|---|
| [Server] 세션(Session)과 인터벌 관리자(Interval Manager)의 차이 및 사용 (with Node.js) (0) | 2024.07.05 |
| [Server] 강의 내용 정리 (3) (Latency, 추측 항법(Dead Reckoning)) (0) | 2024.07.04 |
| [Server] 강의 내용 정리 (2) (Unity, Unity UGUI) (0) | 2024.06.28 |
| [Server] 강의 내용 정리 (1) (net 모듈, Socket event, Buffer 객체) (0) | 2024.06.27 |