목록Categories (1099)
KoreanFoodie's Study
[C++ 게임 서버] 1-17, 18, 19. Lock-Free Stack 핵심 : 1. Lock-Free Stack 은 락을 사용하지 않고 여러 쓰레드가 Stack 에 어떻게 하면 제대로 Push/Pop 을 할 수 있는지를 보여준다. popCount 나 참조권/소유권 개념을 활용해 이를 구현할 수 있다. 2. Lock-Free Stack 은 실제로 활용하기에는 까다로우며, Lock 기반의 자료구조보다 더 빠르고 효율적이라고 장담할 수 없다. 결국 경합이 일어났을 때, 실질적으로 삭제를 하는 쓰레드는 1개이어야 하기 때문이다. 3. compare_exchange_weak 은 compare_exchange_strong 과 비교했을 때, spurious fail 이 일어날 수도 있다는 차이가 있다. 그 sp..
[C++ 게임 서버] 1-16. Lock-Based Stack/Queue 핵심 : 1. Lock-Based Queue/Stack 자료구조를 활용하면, 여러 쓰레드가 해당 자료구조를 활용할 때 불필요한 Lock 을 추가로 잡을 필요가 없이, 싱글쓰레드 환경에서처럼 Push/Pop 을 사용할 수 있다. 2. Push/Pop 함수 내부적으로 lock_guard 를 잡아주면 된다. 3. WaitPop 의 경우, Condition Variable 을 이용하여 구현한다. Queue 나 Stack 같은 자료구조에 여러 쓰레드가 동시에 접근해야 한다고 가정해 보자. 지금까지 배운 것을 응용하면, 각 쓰레드가 특정 자료구조에 접근할 때마다 락을 걸고 풀기를 반복해야 하겠지만... 이는 사실 매우 귀찮을 수 있다. 그냥 ..
[C++ 게임 서버] 1-15. Thread Local Storage 핵심 : 1. Thread Local Storage(TLS) 는, 쓰레드 별로 할당된 독립적인 메모리 공간을 의미한다. 2. TLS 를 이용하면, 각 쓰레드별로 변수를 사용해야 할 때 힙이나 데이터 영역에서 별도의 변수 선언 없이 데이터를 참조할 수 있어 불필요한 Race Condition 을 피할 수 있다. 3. TLS 는 컴파일러 의존적으로 선언할 수도 있지만, C++ 에서는 이제 언어 표준에서 해당 기능을 제공한다. Thread Local Storage 란, 쓰레별로 독립된 메모리 영역을 의미한다. 스택은 아래 그림과 같이 별도의 stack 영역이 존재하고, Heap 과 데이터 영역을 공유하는데... TLS 의 경우, 각 쓰레드별..
[C++ 게임 서버] 1-14. 메모리 모델 (atomic, 코드 재배치) 핵심 : 1. atomic 연산이란, 말 그대로 해당 연산이 '원자적'으로 수행된다는 뜻이다. '원자적' 이라는 것은, 해당 연산 사이에 다른 연산이 끼어들지 않는다는 것을 의미한다. 2. 컴파일러는 여러 이유(주로 최적화)로 코드를 재배치하는데, 이러한 memory order 는 모든 쓰레드들이 '수정 순서' 에 동의한다는 원칙을 지켜야 한다. 3. 대표적으로 다음 세 가지의 메모리 모델 옵션이 있다 : memory_order_relaxed, memory_order_release, memory_order_seq_cst 원자적 연산이란 무엇일까? 멀티쓰레드 환경에서 원자적 연산을 마치 일시적으로 락을 건다고 생각하기 쉽지만, 정확..
[C++ 게임 서버] 1-13. CPU 파이프라인 (feat. 코드 재배치) 핵심 : 1. 컴파일러는 어셈블리어를 배치하는 과정에서 코드 재배치를 할 수 있다. 이 과정에서 의도치 않은 결과가 도출될 수 있다. 2. CPU 파이프라인은 CPU 가 명령어를 효율적으로 하기 위한 시스템이다. CPU 파이프라인에 대해 공부를 하게 되면, 최적화를 위해 컴파일러가 임의로 어셈블리 코드를 재배치한다는 것을 알 수 있다. 싱글 쓰레드에서는 순차적으로 로직이 실행됨이 보장되지만, 멀티 쓰레드에서는 꼭 의도대로 프로그램이 동작하지 않을 수 있는데... 다음 코드를 보자. volatile bool ready = false; // 가시성, 코드 재배치 int32 x = 0; int32 y = 0; int32 r1 = 0;..
[C++ 게임 서버] 1-12. 캐시 (Temporal Locality, Spatial Locality) 핵심 : 1. RAM 으로부터 데이터를 가져올 때, 자주 쓰일 것으로 예상되는 데이터는 캐시에 저장된다 2. 캐싱 철학에는 Temporal Locality 와 Spatial Locality 가 있는데, 각각 '최근에 사용된 녀석은 다시 사용될 확률이 높다' , '메모리상으로 가까이 있는 녀석은 함께 사용될 확률이 높다' 라는 추론을 기반으로 한다. 3. 실제 메모리 조회를 할 때는 위의 Temporal Locality 와 Spatial Locality 를 고려하여 설계를 하는 것이 좋다. 흔히 우리는 캐시를 통해 값을 조회할 때, 우리가 필요한 정보만 얻어올 것이라고 기대하지만, 실제로는 그렇지 않다..
[C++ 게임 서버] 1-11. future, promise, packaged_task 핵심 : 1. future, promise, packaged_task 는 단발성 이벤트를 비동기적으로 실행할 때 유용하다. 2. 세 녀석의 차이는 Level of Control 에 달려 있다. future 는 특정 함수의 작업을 맡기고, packaged_task 는 작업을 만든 다음, 쓰레드에 이를 실어 보낸다. 마지막으로, promise 는 작업을 실어 보내면서, 동시에 실어 보내는 함수 내부에서 future 에 넘길 값을 커스텀하게 세팅할 수 있다. 3. '비동기적'인 작업과 '멀티쓰레드' 는 다른 의미이다. 어떤 작업이 비동기적으로 실행된다고 해서, 꼭 멀티쓰레드를 이용할 필요는 없다. 사실 자주 쓰이는 녀석들은..
[C++ 게임 서버] 1-10. Condition Variable (조건 변수) 핵심 : 1. 조건 변수(Condition Variable) 은 유저 레벨 오브젝트로, 동일한 프로그램 내에서 접근 가능하다. 2. 조건 변수를 이용하면 하나의 쓰레드 혹은 모든 쓰레드에 시그널을 전달할 수 있다. 3. 조건 변수는 Lock 과 페어를 이루어 사용한다! 저번에 Handle 을 이용해 이벤트를 구현했던 것을 조금 발전시켜, 이번에는 condition variable 을 사용해볼 것이다. 사실 condition variable 은 Handle 을 사용하는 것보다 조금 더 우아한(?) 방식이며, 조건을 체크하여 락을 잡고 푸는 기능을 제공해 준다. 또한 condition variable 은 유저 레벨 오브젝트이므로..
[C++ 게임 서버] 1-9. 이벤트 구현 (+ Handle) 핵심 : 1. 이벤트를 사용하면 SpinLock 에 비해 CPU 낭비를 줄일 수 있는 장점이 있다. 2. 이벤트의 개념은 간단하다. 핸들을 만든 후, Consumer 는 핸들을 참고하여 깨울 때까지 준비 상태에 들어가고, Producer 가 준비가 되었을 때 핸들에게 이벤트를 보내기만 하면 된다. 3. 핸들은 커널 오브젝트에 해당하여, 여러 쓰레드가 이를 활용할 수 있다. 이제 Lock 구현을 함에 있어 '무작정 기다리기' 와 '잠깐 기다리기' 를 구현해 보았으니, 마지막 순서가 남아있다. 바로... '제 3자가 깨워주기' 인데, 여기서 '제 3자'라 함은 일반적으로 OS 의 스케줄러를 의미하고, '깨워준다' 는 것은 해당 쓰레드에 시그널을 ..
[C++ 게임 서버] 1-8. Sleep 개념과 구현 핵심 : 1. Lock 을 잡을 수 없을 때, 일정 시간 동안 기다리게 할 때 sleep 을 사용한다. 2. Sleep 을 사용하면 일정 시간 동안 해당 쓰레드는 준비 상태에 있다가 다시 돌아온다. 이 시간동안에는 CPU 자원을 사용하지 않는다. 대신 Kernel Space 로 전환되는 과정에서의 비용은 발생한다(컨텍스트 스위칭). 3. yield 는 쓰레드에게 주어진 Time Slice 를 즉시 반납하도록 하며, 스케줄러에게 쓰레드가 어떤 일을 할지 알아서 정해달라고 요청을 하게 된다. 이전에 Lock 구현 이론에서 설명한 것 중 두번째인, '랜덤 시간 후 복귀' 메타가 바로 sleep 이다. 이전 글에서 SpinLock 을 이용해 '무작정 기다리는..