IOCP 서버강의

2025. 5. 25. 04:19·Study/Server

IOCP 서버강의

게임서버란

  1. 웹서버
    1. 테이크 아웃전문점
    2. 손님이 음식받으면 끝
    3. 질의 응답 형태
    4. 드물게 정보 갱신
    5. 손님이 떠나면, 손님의 상태를 잃어버림 (Stateless)
    6. 웹, 웹서비스 만드는데 많이 사용
  2. 게임서버
    1. 일반식당
    2. 서빙직원이 와서 손님에게 물어보고 추가주문 등…
    3. 실시간 Interaction 있음
    4. 요청갱신 굉장히 많음
    5. 손님이 식당에 머무는동안, 손님의 상태 계속 살펴봄 (Stateful)

게임서버는 게임장르에 따라 요구사항이 많이 달라 게임서버 엔진같은 것을 활용 불가능함

상황에 맞게 최적화하고 직접 코어를 코딩해야함

DeadLock

스레드 2개가 서로 함수를 실행할때 Lock 을 사용하고 그 함수들이 Lock을 서로 필요할떄 교착상태로 멈추게 되는 상태

해결방안 :

  1. Lock의 순서를 정해서 Lock끼리 겹치지 않도록 구현
  2. Lock을 정의할때 숫자를 정해놔서 순서 정하기

Lock 구현 이론

  1. 스핀락 방법 : 계속 Lock을 기다리고 있음
  2. 랜덤으로 자리가 나는지 확인 (랜덤으로 확인하기 떄문에 확인 안하고 있을때 다른 사람이 사용할 가능성 있음)
  3. 다른 직원한테 부탁해서 확인

컨텍스트 스위칭

유저레벨(한식) = 스레드 → 커널레벨(식당관리자) = CPU Core → 일식

으로 작업이 이동하면서 작동되는데 이때 움직이는데 컨텍스트 스위칭 비용이 듬

비용이 드는 이유는 그 작업의 상태에 대한걸 모두 Ram에 저장하고 옮기는 등의 연산이 필요해서

SpinLock

volatile bool 변수이름
처럼 변수 앞에 volatile 을 붙이면 이 변수를 최적화 하지 말라는 뜻임

#include "pch.h"
#include <iostream>
#include "CorePch.h"
#include <mutex>

class SpinLock
{
public:
    void lock()
    {
        // 매 프레임마다 locked 상태를 확인하고 있음
        // 근데 이걸 최적화 하지 않아야 Releas 모드 때도 정상작동함
        bool expected = false;
        bool desired = true;

        // CAS (Compare-And-Swap) Lock 이 사용가능한 상태인지 계속 while문으로 검사하고 
        // 사용가능하면 끝나서 _locked = true 가 실행됨        
        while (_locked.compare_exchange_strong(expected, desired) == false)
        {
            expected = false;
        }
        _locked = true;
    }

    void unlock()
    {
        _locked.store(false);
    }

private:
    atomic<bool>_locked = false;
};

int32 sum = 0;
mutex m;
SpinLock spinLock;

void Add() {
    for (int32 i = 0; i < 10'0000; i++) {
        lock_guard<SpinLock> gard(spinLock);
        sum++;
    }
}

void Sub() {
    for (int32 i = 0; i < 10'0000; i++) {
        lock_guard<SpinLock> gard(spinLock);
        sum--;
    }
}

int main()
{
    thread t1(Add);
    thread t2(Sub);

    t1.join();
    t2.join();

    cout << sum << endl;
}

Sleep

 

프로그램이 스레드 → 준비 → 대기 → 실행

→ 종료 되는 과정

while (_locked.compare_exchange_strong(expected, desired) == false)
        {
            expected = false;

            // 100ms 동안 sleep 해서 스케쥴링 확인 100ms 나중에 다시함
            this_thread::sleep_for(100ms);

            // 양보한다 = 현재 실행중인 스레드가 실행을 포기하고 다시실행을 위해 스케줄링되도록 만든다
            this_thread::yield();
        }

Event

  1. Auto Reset Event
  2. Manul Reset Event

두가지 방식이 있음

Win Api 중 CreateEvent 함수가 이벤트 생성가능 (커널 오브젝트임)

::CreateEvent ( 보안속성, 자동&수동으로 리셋을 할지 정하는 bool 값, 상태 (빨간불,파란불) 초기값, 이름)

HANDLE handle = ::CreateEvent( NULL, false, false, NULL );

handle 은 정수값인데 일종의 번호표 같은거임, 많은 이벤트들의 식별자

사용법

  1. ::SetEvent(handle) 하면 해당 handle에 있는 이벤트를 Signal (파란불) 상태로 바뀐다
  2. ::WaitForSingleobject(handle, INFINITE); 하면 커널 오브젝트의 상태를 보면서 빨간불이면 이 라인에서 대기 상태가 되고 파란불이 되면 이때부터 다음 라인으로 넘어가짐

자동리셋으로 설정하면 다음 라인 넘어갈떄 자동으로 Signal 빨간불

결론 : 무한 While문을 돌면서 계속 if문 검사를 하는것 보다는 제 3자인 커널 오브젝트로 필요할때만 호출되도록 구현하면 좋지만 상황에 맞지 않게 사용한다면 비효율적 일수도 있음

ConditionVariable

queue<int32> q;
mutex m;
HANDLE handle;

// 참고) CV는 User-Level Object (커널 오브젝트가 아님)
condition_variable cv;

void Producer()
{
    while (true)
    {
        // 1. Lock을 잡고
        // 2. 공유 변수값을 수정
        // 3. Lock을 풀고
        // 4. 조건변수를 통해 다른 스레드로 통지

        {
            unique_lock<mutex> lock(m);
            q.push(100);
        }

        cv.notify_one();    // Wait 중인 스레드가 있으면 딱 1개를 깨운다
        //::SetEvent(handle);
    }

}

void Consumer()
{
    while (true)
    {
        unique_lock<mutex> lock(m);
        cv.wait(lock, []() {return q.empty() == false; });    // 람다, 함수 등을 넣으면됨, 여긴 람다씀
        // 1. Lock 을 잡고
        // 2. 조건 확인
        // 3. 조건 == 만족 -> 빠져나와서 이어서 코드 진행 / 조건 != 만족  -> Lock을 풀어주고 대기 상태 

        // 그런데 notify_one()을 했으면 항상 조건식을 만족하는거 아닐까?
        // -> Spurious Wakeup (가짜 기상) 으로 notify_one 할때 lock을 잡고 있는 것이 아니기 때문에 
        //::WaitForSingleObject(handle, INFINITY);

        {
            int32 data = q.front();
            q.pop();
            cout << q.size() << endl;
        }
    }
}

Future

// 동기실행 방식
    int64 sum = Calculate();   
    cout << sum << endl;

    // 만약 Calculate 함수가 굉장히 오래걸리는 함수라면 여기서 대기시간이 길어서 thread로 따로 뺴서 계산
    thread t(Calculate);
    t.join();

하지만 큰 기능은 아니고 오래걸리는 함수라 무거운 thread 만들고 추가하는것 말고 새롭게 가볍게 사용가능한 future 기능이 있음

void worker(std::promise<string>* p) {
  // 약속을 이행하는 모습. 해당 결과는 future 에 들어간다.
  p->set_value("some data");
}

int main() {
  std::promise<string> p;

  // 미래에 string 데이터를 돌려 주겠다는 약속.
  std::future<string> data = p.get_future();

  std::thread t(worker, &p);

  // 미래에 약속된 데이터를 받을 때 까지 기다린다.
  data.wait();

  // wait 이 리턴했다는 뜻이 future 에 데이터가 준비되었다는 의미.
  // 참고로 wait 없이 그냥 get 해도 wait 한 것과 같다.
  std::cout << "받은 데이터 : " << data.get() << std::endl;

  t.join();
}

캐시

RAM ↔ CPU 이동하는 시간이 CPU처리시간에 비하면 굉장히 오래걸림 그래서 캐시라는걸 도입

CPU 구조 → 코어 → 연산장치, 캐시장치
캐시 계급 → 1. 레지스터 → 2. L1캐시 → 3. L2캐시

그래서 중간 저장소인 캐시에 임시 저장 후 ↔ RAM 주고 받음

#include <Windows.h>

int32 buffer[10000][10000];

int main()
{
    memset(buffer, 0, sizeof(buffer));

    {
        uint64 start= GetTickCount64();

        int64 sum = 0;
        for (int32 i = 0; i < 10000; i++)
            for (int32 j = 0; j < 10000; j++)
                sum += buffer[i][j];    // 차이점

        uint64 end = GetTickCount64();

        cout << "TICK COUNT " << (end - start) << endl;
        // 140 정도 나옴
    }

    {
        uint64 start = GetTickCount64();

        int64 sum = 0;
        for (int32 i = 0; i < 10000; i++)
            for (int32 j = 0; j < 10000; j++)
                sum += buffer[j][i];    // 차이점

        uint64 end = GetTickCount64();

        cout << "TICK COUNT " << (end - start) << endl;
        // 730 정도 나옴
    }
}

캐시는 인접한 데이터일 경우 더더욱 활용하기 때문에 윗버전 sum += buffer[i][j] 경우 인접한 데이터 이므로 캐시→힛→캐시→힛 으로 굉장히 빠름

[][][][] [][][][] [][][][] [][][][] 이중배열이라면 이런식으로 []배열의 연속이므로 윗버전이 인접함

CPU파이프라인

int32 x = 0;
int32 y = 0;
int32 r1 = 0;
int32 r2 = 0;

void Thread_1()
{
    y = 1;    // Store y
    r1 = x;   // Load x
}

void Thread_2()
{
    x = 1;    // Store x
    r2 = y;   // Load y
}

int main()
{
    int32 count = 0;
    while (true)
    {
        ready = false;
        count++;
        x = y = r1 = r2 = 0;

        thread t1(Thread_1);
        thread t2(Thread_2);

        ready = true;
        t1.join();
        t2.join();

        if (r1 == 0 && r2 == 0)
            break;
}

위 코드를 실행하면 멀티스레드로 2개의 Thread 를 돌리며 x=1, y=1을 동시에 그리고 r1=x, r2=y 를 동시에 실행해서 r1, r2 가 0이 될리가 없는데 가끔 CPU 의 컴파일러가 성능 최적화를 자동으로 하면서 Sotre, Load 의 순서를 다르게 해줄 때가 있음 그 떄 break 가 됨

CPU 실행 순서

이 순서대로 실행되지만 컴파일러가 만약 Store, Load 같은 구문의 순서가 다르면 더 효율적이다 라고 판단하면 언제든지 순서를 다르게 실핼 할 가능성이 있음 (지금 까지는 단일스레드라 몰랐지만 멀티스레드 환경에서는 문제가 될 수도 있어서 C++ 11 부터 멀티스레드 환경에 대한 지원을 많이 해줘서 쉽게 보완가능)

메모리 모델



변수선언을 atomic 으로 했다면 수정순서가 동일하므로 절대시간이 지나갈 동안 스레드가 읽는 변수값은 동일한 시간이라면 여러 스레드가 동시에 읽는다면 동일한 값임

int main()
{
    atomic<bool> flag = false;

    flag.is_lock_free();
    flag.store(true, memory_order::memory_order_seq_cst);

    bool val = flag.load(memory_order::memory_order_seq_cst);

    // 예전 flag 값을 prev에 넣고, flag 값 수정
    {
        // 이방법 써야함
        bool prev = flag.exchange(true);

        // 이방법 쓰면안됨
        bool prev = flag;
        flag = true;
    }

    // CAS (Compare-And-Wap) 조건부 수정
    {
        bool expected = false;
        bool desired = true;
        flag.compare_exchange_strong(expected, desired);
        // compare_exchange_strong 풀어서 설명하면 아래 코드와 같음
        if (flag == expected)
        {

            //다른스레드의 interrruption 을 받아서 중간에 실패 할 수 있음 
            //(이럴때 compare_exchange_weak 면 그냥 return 하고 strong 이면 다시 반복함)
            //if (묘한상황)
                //return false;

            flag = desired;
            return true;
        }
        else
        {
            expected = flag;
            return false;
        }
}

    // Memory Model (정책)
    // 1. Sequentialy Consistent (seq_cst)    일관성이 있다
    // 2. Acquire-Release (consume, scquire, release, acq_rel)
    // 3. Relaxed (relaxed)

    // 1. seq_cst    (가장 엄격= 컴파일러 최적화 여지 적음 = 직관적)
    // 2. acquire-release (딱 중간, release 명령이전의 메모리 명령들이 해당 명령 이후로 재비치 되는것 금지)
    // 3. relaxed    (가장 자유롭 = 컴파일러 최적화 여지 많음 = 직관적이지 않음, 자유로워서 코드 재배치 마음대로, 가장 기본조건만 지킴)

    // 인텔, AMD 의 경우 애당초 순차적 일관성을 보장해서 seq_cst 써도 별다른 부하가 없음
    // 하지만 AMD는 좀 차이가 있다고 함

Thread Local Storage

Untitled

TLS : Thread 마다 할당되어 있는 전역 메모리

// 그냥 int32 이라면 다른 스레드가 이 변수를 사용하는 순간 값이 변하지만
// thread_local로 변수를 선언하면 스레드마다 공간을 따로따로 가지고 있음
thread_local int32 LThreadId = 0;

void ThreadMain(int32 threadId)
{
    LThreadId = threadId;

    while (true)
    {
        cout << "HI!! i'm Thread" << LThreadId << endl;
        this_thread::sleep_for(1s);
    }
}

int main()
{
    vector<thread> threads;
    for (int i = 0; i < 10; i++)
    {
        int32 threadId = i + 1;
        threads.push_back(thread(ThreadMain, threadId));
    }

    for (thread& t : threads)
        t.join();
}

Lock-Based Stack / Queue

#pragma once

#include <mutex>

template<typename T>
class LockStack
{
public:
    LockStack() { }
    // 자료구조 이므로 복사연산자로 복사가 될경우 삭제하여
    // 나중에 실수로 복사했을때 컴파일러에서 오류가 나도록 함
    LockStack(const LockStack&) = delete;
    LockStack& operator=(const LockStack&) = delete;

    void Push(T value)
    {
        // Lock을 걸고
        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;

        // empty -> top -> pop 순서를 거치는게 C++ 원칙
        // 특히 자료구조는 서버에서 사용하다가 오류 발생하는 경우 있음
        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() == false; });
        value = std::move(_stack.top());
        _stack.pop();
    }

private:
    stack<T> _stack;
    mutex _mutex;
    condition_variable _condVar;
};
#pragma once
#include <mutex>

template<typename T>
class LockQueue
{
public:
    LockQueue() { }

    LockQueue(const LockQueue&) = delete;
    LockQueue& operator=(const LockQueue&) = delete;

    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() == false; });
        value = std::move(_queue.front());
        _queue.pop();
    }

private:
    queue<T> _queue;
    mutex _mutex;
    condition_variable _condVar;
};

LockFreeStack1

#pragma once

#include <mutex>

template<typename T>
class LockStack
{
public:
    LockStack() { }

    LockStack(const LockStack&) = delete;
    LockStack& operator=(const LockStack&) = delete;

    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;

        // empty -> top -> pop 순서를 거치는게 C++ 원칙
        // 특히 자료구조는 서버에서 사용하다가 오류 발생하는 경우 있음
        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() == false; });
        value = std::move(_stack.top());
        _stack.pop();
    }

private:
    stack<T> _stack;
    mutex _mutex;
    condition_variable _condVar;
};

template<typename T>
class LockFreeStack
{
    struct Node
    {
        // 템플릿 타입 T형태의 값을 받아오고 그 값을 data안에 넣어서 초기화
        Node(const T& value) : data(value).next(nullptr)
        {

        }

        T data;
        Node* next;
    };

public:
// 1.새노드 만들고
// 2.새노드의 next = head
// 3.head = 새노드

// [][][][][]
// [head]
    void Push(const T& valeue)
    {
        Node* node = new Node(valeue);
        node->next = _head;

        // compare_exchange_weak 을 풀어쓴 코드
        // if (_head == node->next)
        // {
        //     _head = node;
        //     return true;
        // }else {
        //     node->next = _head;
        //     return false;
        // }
        while (_head.compare_exchange_weak(node->next, node) == false)
        {
            // node->next = _head;
        }
        _head = node;
    }

// [TRYPOP 의 단계]
// 1. head 읽기
// 2. head->next 읽기
// 3. head = head->next
// 4. data 추출해서 반환
// 5. 추출한 노드를 삭제

// [][][][][]
//    [head]
    bool TryPop(T& value)
    {
        // 1~3
        _popCount++;
        Node* oldHead = _head;

        // [compare_exchange_weak 의 설명]
        // if(_head == oldHead)
        // {
        //     _head = oldHead->next;
        //     return true;
        // }
        // else 
        // {
        //     oldHead = _head;
        //     return false;
        // }
        // false 동안은 값을 변화시키지 않으며 계속 while문을 돌아 값이 변경될때 까지 대기
        while (oldHead && _head.compare_exchange_weak(oldHead, oldHeld->next) == false)
        {}
        if (oldHead == nullptr)
            return false;
        // 4단계
        value = oldHead->data;

        // 5단계
        TryDelete(oldHead);        
        return true;

    }

// 1.데이터분리
// 2.Count 체크
// 3.나혼자면 삭제
    void TryDelete(Node* oldHead)
    {
        // 나 외에 누가 있는지?
        if (_popCount == 1)
        {
            // 나 혼자네?

            // 이왕 혼자인거, 삭제 예약된 다른 데이터들도 삭제하자
            Node* node = _pendingList.exchange(nullptr);

            if(--_popCount == 0)
            {
                // 끼어든 애가 없음 -> 삭제 진행
                // 이제와서 끼어들어도, 어차피 데이터는 분리한 상태
                DeleteNodes(node);
            }
            else if (node)
            {
                // 누가 끼어들었으니 다시 갖다 놓자
                ChainPendingNodeList(node);
            }
            // 그래 내 데이터 삭제
            delete oldHead;
        }
        else 
        {
            // 누가 있네? 그럼 지금 삭제하지 않고, 삭제 예약만
            ChainPendingNode(oldHead);
        }

        // [][][][][][]-> [][][][] 이어주는 함수
        void ChainPendingNodeList(Node* first, Node* last)
        {
            last->next = _pendingList;

            while (_pendingList.compare_exchange_weak(first->next, last) == false)
        }

        void ChainPendingNodeList(Node* node)
        {
            Node* last = node;
            while (last->next)
                last = last->next;

            ChainPendingNodeList(node, last);
        }

        void ChainPendingNode(Node* node)
        {
            ChainPendingNodeList(node, node);    // 하나짜리로 들어가 호출
        }

        void ChainPendingNode(Node* node)
        {
            ChainPendingNodeList(node, node);
        }

        // [][][][][]
        static void DeleteNodes(Node* node)
        {
            while(node)
            {
                Node* next = node->next;
                delete node;
                node = next;
            }
        }
    }
private:
    // [][][][]][]
    // [head]
    atomic<Node*> _head;

    // Pop 을 실행중인 쓰레드 개수
    atomic<uint32> _popCount = 0;
    // 삭제 되어야 할 노드들 (첫번째 노드)
    atomic<Node*> _pendingList;
};

Shared Pointer 가 아닌 데이터 타입형태의 LockFreeStack

LockFreeStack2

#pragma once

#include <mutex>

template<typename T>
class LockFreeStack
{
    struct Node
    {
        // 템플릿 타입 T 형태로 값을 받아오고 그 T형태로 sharedPointer를 만든 후 그 value를 data에 넣어줌 
        // next 변수는 nullptr 로 초기화 함
        Node(const T& value) : data(make_shared<T>(value)).next(nullptr)
        {

        }
        shared_ptr<T> data;
        shared_ptr<Node> next;
    };

public:
    void Push(const T& value)
    {
        shared_ptr<Node> node = make_shared<Node>(value);
        node->next = _head;

        // 변환
        while (atomic_compare_exchange_weak(&_head, &node->next, node)== false)
        {}
        // while (_head.compare_exchange_weak(node->next, node) == false)
        // {}
        _head = node;
    }

    shared_ptr<T> TryPop()
    {
        // atomic 을 꼭 해야함
        shared_ptr<Node> oldHead = atomic_load(&_head);

        while(oldHead && atomic_comapre_exchange_weak(&_head, &oldHead, oldHead->next) == false)
        {}

        if((oldHead) == nullptr)
            return shared_ptr<T>();

        return oldHead->data;
    }

private:
    shared_ptr<Node> _head;

    // Pop 을 실행중인 쓰레드 개수
    atomic<uint32> _popCount = 0;
    // 삭제 되어야 할 노드들 (첫번째 노드)
    atomic<Node*> _pendingList;
};

Atomic 버전

#pragma once

#include <mutex>

template<typename T>
class LockFreeStack
{
    struct CountedNodePtr
    {
        int32 externalCount = 0;
        Node* ptr = nullptr;
    }

    struct Node
    {
        // 템플릿 타입 T 형태로 값을 받아오고 그 T형태로 sharedPointer를 만든 후 그 value를 data에 넣어줌 
        // next 변수는 nullptr 로 초기화 함
        Node(const T& value) : data(make_shared<T>(value))
        {

        }
        shared_ptr<T> data;
        atomic<int32> internalCount = 0;
        CountedNodePtr next;
    };

public:
    void Push(const T& value)
    {
        CountedNodePtr node;
        node.ptr = new Node(value);
        now.externalCount = 1;

        node.ptr->next = _head;
        while(_head.comapre_exchange_weak(node.ptr->next, node) == false)
        {}
    }

    shared_ptr<T> TryPop()
    {
        CountedNodePtr oldHead = _head;
        while(true)
        {
            // 참조권 획득 (externalCount를 한 시점 기준 +1 한 애가 이김)
            IncreaseHeadCount(oldHead);
            // 최소한 externalCount는 >= 2 일테니 삭제x (안전하게 접근할 수 있는)
            Node* ptr = oldHead.ptr;

            // 데이터 없음
            if (ptr == nullptr)
               return shared_ptr<T>();

            // 소유권 획득 (ptr->next로 head를 바꿔치기 한 애가 이김)
            if (_head.compare_exchange_strong(oldHead, oldHead.ptr->next))
            {
                shared_ptr<T> res;
                res->swap(ptr->data);

                // external : 1 -> 2
                // internal : 0

                // 나 말고 또 누가 있는가?
                const int32 countincrease = oldHead.externalCount - 2;

                // fech_add -> 더한뒤에 더하기 전 값 return 함
                if(ptr->internalCount.fetch_add(countincrease) == -countincrease)
                    delete ptr;

                return res;
            }
            else if(ptr->internalCount.fetch_sub(1) == 1)
            {
                // 참조권은 얻었으나, 소유권은 실패 -> 뒷수습은 내가 한다.
                delete ptr;
            }
        }
    }

private:
    shared_ptr<CountedNodePtr> _head;

    void IncreaseHeadCount(CountedNodePtr& oldCounter)
    {
        while (true)
        {
            CountedNodePtr newCounter = oldCounter;
            newCounter.externalCount++;

            // while 로 무한루프를 하지 않으므로 strong 으로
            if (_head.compare_exchange_strong(oldCounter, newCounter))
            {
                oldCounter.externalCount = newCounter.externalCount;
                // 성공했으면 빠져나가라
                break;
            }
        }
    }
};

SharedPtr + Councurrecy 이슈 없이 가능한 버전

ThreadManager

class ThreadManager
{
public:
    ThreadManager();
    ~ThreadManager();

    void    Launch(function<void(void)> callback);
    void    Join();

    static void InitTLS();
    static void DestroyTLS();

private:
    Mutex            _lock;
    vector<thread>    _threads;
};
ThreadManager::ThreadManager()
{
    // Main Thread
    InitTLS();
}

ThreadManager::~ThreadManager()
{
    Join();
}

// 함수포인터가 아닌 모든 함수형태를 받을 수 있는 형태임
void ThreadManager::Launch(function<void(void)> callback)
{
    LockGuard guard(_lock);

    // thread 실행인데 람다로 집어 넣음 
    _threads.push_back(thread([=]()
    {
        InitTLS();
        callback();
        DestroyTLS();
    }));
}

void ThreadManager::Join()
{
    for (thread& t : _threads)
    {
        if (t.joinable())
            t.join();
    }
    _threads.clear();
}

void ThreadManager::InitTLS()
{
    // 전역변수로 만듬
    static Atomic<int> SThreadId = 1;
    LThreadId = SThreadId.fetch_add(1);
}
// MAIN 부분

CoreGlobal Core;

void ThreadMain()
{
    while (true)
    {
        cout << "HI I AM THREAD" << LThreadId << endl;
        this_thread::sleep_for(1s);
    }

}
int main()
{
    for (int32 i = 0; i < 5; i++)
    {
        GThreadManager->Launch(ThreadMain);
    }
    GThreadManager->Join();
}

Reader-Writer 

'Study > Server' 카테고리의 다른 글

용량 정리  (0) 2022.06.16
'Study/Server' 카테고리의 다른 글
  • 용량 정리
_WooHyun_
_WooHyun_
  • _WooHyun_
    Nerd
    _WooHyun_
  • 전체
    오늘
    어제
    • 분류 전체보기 (79)
      • Study (60)
        • Algorithm (24)
        • Unreal Engine (19)
        • C++ (1)
        • Maya (1)
        • GoLang (3)
        • Mysql (3)
        • Linux (7)
        • Server (2)
      • Projects (0)
        • Unreal Engine (0)
        • Server (0)
      • 개발일지 (8)
        • Unreal Engine (7)
        • Art (1)
        • Server (0)
      • 미래 (5)
      • 개발아이디어 (0)
      • 잡지식 (2)
  • 블로그 메뉴

    • 홈
    • 방명록
    • 글쓰기
    • 블로그설정
  • 링크

    • GitHub
  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
_WooHyun_
IOCP 서버강의
상단으로

티스토리툴바