전체 글 38

1. Reference Counting

객체를 생성시키고 소멸시킬 때 문제점이 있다. A라는 객체가 B라는 객체를 참조하고 있는 경우 B라는 객체가 용도를 다해서 소멸시키게 되면 A는 이상한 메모리 공간을 참조하게 되어버린다. 이를 해결하기 위해 Reference Counting을 하는데 자신을 참조하고 있는 수를 센 후 0이 되었을 때 소멸시키는 것이다. 코드를 보면 더욱 이해하기 쉬울 것이다.RefCounting.h#pragma once//-----------------// RefCountable//-----------------class RefCountable{public: RefCountable() : _refCount(1) {} virtual ~RefCountable() {} // 최상위 클래스의 소멸자에는 virtual(메모리 릭..

17. DeadLock 탐지

전에도 말했듯 데드락을 탐지하는 방법 중 사이클의 존재를 확인하는 방법이 있다.락을 잡을 때 방향그래프를 만들고 이를 DFS로 탐색하여 확인한다. DeadLockProfiler.h#pragma once#include #include #include //--------------------// DeadLockProfiler//--------------------class DeadLockProfiler{public: void PushLock(const char* name); void PopLock(const char* name); void CheckCycle();private: void Dfs(int32 index);private: unordered_map _nameToId; unordered_map _i..

16. Reader-Writer Lock

이제 서버 엔진부에서 사용할 락을 만들어 보자. 지금까지 사용했던 표준 mutex는 일단 재귀적으로 락을 잡을 수 없다. 이부분은 재귀적으로 락을 잡을 수 있도록 하는 recursive_mutex를 사용하면 된다고 할 수도 있지만 다른 문제가 있다.변하지 않는 데이터가 있고 모든 스레드가 이 데이터를 읽기만 한다면 굳이 락을 걸어서 사용할 필요가 없다. 하지만 굉장히 드물게(예를들어 1주일 또는 1달) 데이터에 변동이 있다면 분명 충돌이 날 것이고 이를 방지하기 위해서는 락을 걸어주어야 하는데 고작 이 1번을 위해서 락을 걸어서 사용해야 하나?라고 생각할 수 있을 것이다.이때 사용하는 것이 Reader-Writer Lock이다. read할 때에는 락을 걸지 않은 것처럼 사용하다가 write할 때만 락을 ..

14. Lock-Free Stack #2

bool TryPop(T& value){ Node* oldHead = _head; while (oldHead && (_head.compare_exchange_strong(oldHead, oldHead->next) == false)) { } if (oldHead == nullptr) return false; value = oldHead->data; //문제 발생 delete oldHead; return true;}결국은 oldHead가 가르키고 있는 노드를 아무 스레드도 참조하고 있지 않을 때 비로소 메모리에서 해제를 시킬 수 있다.  bool TryPop(T& value){ Node* oldHead = _head; while (oldHead && (_head.compare_exchange_strong(..

13. Lock-Free Stack #1

전에 락 기반 큐/스택을 만들어 보았는데 이번에는 락을 사용하지 않는 스택에 대해 알아보자.Lock-Free는 이름만 보면 락을 사용하지 않아 더 빠를 것이라고 생각이 되는데 실상은 그렇지 않다.락을 사용하지 않기 때문에 동기화 작업을 해주어야 하고 그로 인해서 성능 차이가 거의 나지 않는다고 볼 수 있다. 락 프리 스택은 락 기반 스택처럼 기존의 스택을 락으로 감싸는 형식이 아니라 아예 새로 만들어야 한다.templateclass LockFreeStack{ struct Node { Node(const T& value) :data(value) { } T data; Node* next; };public:private: atomic _head;};기본적인 스택의 틀이다. 이제 Push와 Pop을 만..

12. Lock-Based Queue/Stack

지금까지 배운 내용들로 간단하게 멀티스레드 환경에서 사용할 수 있는 큐와 스택을 알아보자. #include #include queue q;stack s;void Push(){ while (true) { int value = rand() % 100; q.push(value); this_thread::sleep_for(10ms); }}void Pop(){ while (true) { if (q.empty()) continue; int data = q.front(); q.pop(); cout 데이터를 push하는 스레드와 pop하는 스레드를 만들었다. 당연하게도 위의 코드는 크래시가 나버린다. 같은 공용 데이터에 동시에 접근하기 때문에 생기는 문제이다. 그렇다면 Push와 Pop을 할 때 락을 ..

11. Thread Local Storage

Thread Local Storage, TLS란 스레드들이 공용으로 접근하는 힙(heap)영역과는 다르게 스레드만의 독립된 공간이다. 각 스레드마다 스택이라는 독립된 공간이 존재하지만 TLS와는 차이점이 분명히 존재한다. 스택은 일반적으로 함수들의 공간이라고 본다. 함수가 호출되고 종료되면서 메모리에 올라갔다 없어졌다 하는 불안정한 공간이기 때문에 전역변수와 같은 영구적으로 사용할 데이터를 저장하는 것은 위험하다. 따라서 TLS라는 공간이 존재하는 것인데 전역변수 같은 데이터를 저장하는데 사용되는 공간이다. 물론 각 스레드만의 독립된 공간이므로 다른 스레드에서는 전역변수에 접근할 수 없고 오로지 해당 스레드에서만 접근이 가능하다. thread_local int TLS; TLS를 사용하는 방법은 간단하다...

10. 메모리 모델

전에 공부했던 것들을 복습해보자- 여러 스레드가 동일한 메모리에 동시에 접근할 때, 그 중 write 연산에서 문제가 발생한다.- Race Condition(경합 조건)이 발생한다.- 이러한 행위를 Undefined Behavior(정의되지 않은 행동)이라고 한다.   * Lock(mutex)를 이용하여 상호배제(mutual exclusion)을 만족시킨다.   * Atomic(원자적) 연산을 이용한다. c++은 "atomic 연산에 한해, 모든 스레드가 동일 객체에 대해서 동일 수정 순서를 관찰"하도록 보증한다고 한다. atomic 연산 즉, 원자적인 연산이 꼭 Atomic 키워드를 사용하는 것이 아니라 cpu가 한 번에 처리할 수 있는 연산을 말한다.이는 환경에 따라 달라지는데 예를 들어 8byte의..

9. Future

전 게시글의 조건변수는 생산자, 소비자와 같이 한 스레드에서는 데이터를 밀어넣고 다른 스레드에서는 데이터를 꺼내 쓰는 방식에서 유용하게 작동한다는 것을 알 수 있었다. 하지만 단발성으로 이벤트를 보내줘야할 상황이 생길 수 있는데 이럴 때 조건변수까지 사용하지 않고 더 가볍게 처리할 수 있는 방법이 있다. 그것이 Future, 미래 객체이다. #include int Calculate(){ int sum = 0; for (int i = 0; i 10000미만의 자연수를 더하는 단순한 함수가 있다. 실행 순서를 보게되면 Calculate()함수가 호출 될 때 Calculate() 내부로 들어가서 코드를 실행하고 sum을 반환하면서 다시 main으로 돌아와 출력하게 될 것이다. 즉 함수가 호출 될 때 main..