반응형
처음으로 멀티 스레드 환경으로 들어가 보는 거.
cout << "Hello World!" << endl;
이런 애들 사실 시스템 콜이라해서 운영체제 커널에 요청해서, 운영체제가 요청받은 hello world를 출력한다는 걸 해주고 있는거.
마찬가지로 스레드를 생성하는 것도 유저레벨에서 멋대로 할 수 있는게 아니라 결국에는 운영체제에 요청을 해서 운영체제가 처리를 해줘야 한다.
운영체제에 요청하려면 운영체제마다 제공하는 API 함수들이 있을 것이고, 그것를 활용해서 쓰레드를 만들어야 하는데, 윈도우 리눅스 달라.
윈도우즈 환경이니
#include <windows.h> 하고 ::CreateThread() 를 해서 만들어 활용할 수 있어.
하지만 윈도우에 종속적인 API를 활용하게 되면 혹시라도 나중에 리눅스로 넘어갈 때 수정할 내용 많아져.
그런데 C++11 부터 thread 생성이 표준에 들어왔다.
#include <thread> 라는 헤더를 추가하게 되면 리눅스건 윈도우건 상관 없이 돌아가는 코드 작성할 수 있어.
어지간 해서는 이런 모던한 방법 이용하길 추천
서버는 클라이언트와 다르게 안드로이드, IOS 같이 동시 실행하는 개념이 아니라 서버 환경 하나를 찍고 구동 되게 작업 하겠지만 그럼에도 먼 미래에 다른 운영체제에 호환되는 서버 만들고 싶다면 공용적인 라이브러리 이용하는게 좋다.
한국에서는 윈도우 서버 많이 사용하는 추세야. 해외는 리눅스를 선호해.
void HelloThread()
{
cout << "Hello Thread" << endl;
}
int main()
{
std::thread t(HelloThread);
}
이렇게 하면 새로운 thread가 생성되고 HelloThread코드를 실행하고 종료 됨.
만약에
int main()
{
HelloThread();
}
이렇게 실행 했다고 하면,
main 함수에 와서 HelloThread 함수를 호출하고 빠져 나와서 아래로 넘어가는 개념
근데 그게 아니라 새로운 스레드를 만들어서 실행하면
main 스레드는 계속 실행되는 동시에 다른 스레드가 새로 만들어져서 병렬로 HelloThread 부분이 같이 실행되는 그런 개념이다.
동시에 실행 될 수 있다는 의미
void HelloThread()
{
cout << "Hello Thread" << endl;
}
int main()
{
std::thread t(HelloThread);
cout << "Hello Main" << endl;
}
이걸 ctrl + f5로 실행해보면

에러가 발생한다.
이유는 thread 객체를 하나 만들었는데, t라는 변수로 thread 관리하고 있는건데
이 main thread가 먼저 끝나버리면 에러가 나게 된다.
main thread쪽에서 호출한 애를 책임지고 끝날 때 까지 기다려 주면 된다.
그 때 대표적으로 사용하는 함수가
t.join();
t가 끝날 때 까지 기다려 주겠다는 의미.
언제 끝날까? HelloThread 함수가 끝날 때
만약 HelloThread에 while문을 넣어 무한루프 돌게 하면 무한정 기다리게 된다.
void HelloThread()
{
cout << "Hello Thread" << endl;
}
int main()
{
std::thread t(HelloThread);
cout << "Hello Main" << endl;
t.join();
}

t.join(); 넣은 상태로 실행하며 무사히 실행된다.
HelloThread()에 break point를 잡고 f5를 눌러 실행해 보면

스레드가 여러개 있는 걸 알 수 있다.

주 스레드는 t.join()에 멈춰 있어.

ucrtbased.dll 스레드는 중단점에 멈춰있어.
MMO서버를 만들게 되면 이렇게 다양한 스레드들이 목록에 뜰 테니 break point를 잡아서 이 시점에는 다른 애들은 무슨 일을 하는지 왔다 갔다 하면서 살펴 볼 수가 있다.
thread 클래스에서 자주 활용하는 기능들
t.hardware_concurrency();
t.get_id();
t.detach();
t.joinable();
t.join();
이렇게 5가지만 알면 신경 안써도 된다.
int32 count = t.hardware_concurrency(); // CPU 코어 개수?
100 프로 확률로 정확하진 않고 정보 추출 못하면 0을 리턴하는 경우도 있어.

t.get_id(); // 스레드마다 id
스레드 사이에서 안겹치므로 구분에 사용

t.detach(); // std::thread 객체에서 실제 스레드를 분리
만들어준 thread객체라는 t라는 애랑 실질적으로 구동된 스레드랑 연결고리를 끊어준다는 얘기가 되는 거. 백그라운드 스레드로 독립적으로 동작.
t라는 애를 이용해서 만들어준 스레드의 상태나 정보를 추출할 수 없어지기 때문에 사실 어지간해서는 활용할 일이 없을거야. 딱히 기억 안해도 됨.
t.joinable(); //
std::thread t(HelloThread); 를 한 스텝에 반드시 해야 하는 건 아니고
std::thread t;
t = std::thread(HelloThread);
이렇게 뒤늦게 실질적으로 thread 구동이 되게 만들어주고
std::thread t; 이렇게 객체 자체는 미리 만들어 줄 수 있다. 이 상태에서는 아무런 기능도 하지 않는 거.
만약 여기서 auto id = t.get_id(); 로 id를 체크하게 되면, id 가 0인 상태로 들어오게 될거야 .

joinable이라는 거는 id가 0인지 아닌지를 체크하고 있다고 보면 된다.
_NODISCARD bool joinable() const noexcept {
return _Thr._Id != 0;
}
실질적으로 객체가 관리하고 있는 스레드가 살아있는지 있는지, 없는지를 판별하기 위해서 joinable이라는 거를 활용할 수 있다.
if(t.joinable())
t.join();
실제로 뭐가 연결이 되어 있으면 기다려라 라는 이런 방식으로 사용하는게 일반적이다.
사실 필수적으로 기억할 거는 thread로 만들고, joinable, join으로 기다리고 밖에 없다.
std::thread t;
t = std::thread(HelloThread);
이렇게 따로 하는게 종종 필요할 때가 있다.
vectror<std::thread> v;
v.resize(10);
이렇게 한 다음에 하나하나씩 구동하는 경우에는
먼저std::thread t; 로 빈 객체를 만들어주고
그 다음에 실질적으로 thread를 연동하는 방법이 유용하게 작동할 수 있어.
하지만 일반적으로는
std::thread t(HelloThread); 이렇게 한번에 만들어 주는 것도 나쁘지 않아.
HelloThread에 인자가 추가가 되면 어떨까?
void HelloThread_2(int32 num)
{
cout << num << endl;
}
그럴 때는
std::thread t(HelloThread_2, 10);
이렇게 쉽표를 하고 인자를 추가해 주면 된다.
template <class _Fn, class... _Args, enable_if_t<!is_same_v<_Remove_cvref_t<_Fn>, thread>, int> = 0>
_NODISCARD_CTOR explicit thread(_Fn&& _Fx, _Args&&... _Ax) {
_Start(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...);
}
thead 객체에서 만들어 줄 때 템플릿으로 받아주고 있으니 꼭 함수일 필요는 없고, 어떤 callable 타입도 받을 수 있어 functor나 lambda도 받을 수 있어.
그리고 ... 이라는 문법도 들어가 있는데 C++11추가된 variable template인데 한마디로 인자 개수가 자유자재로 변할 수 있다는 거.
void HelloThread_2(int32 num)
{
cout << num << endl;
}
int main()
{
vector<std::thread> v;
for (int32 i = 0; i < 10; i++)
{
v.push_back(std::thread(HelloThread_2, i));
}
for (int32 i = 0; i < 10; i++)
{
if(v[i].joinable())
v[i].join();
}
cout << "Hello Main" << endl;
}
이렇게 실험하는 걸 만들어 줄 수 있어.

이렇게 출력 되는 거 볼 수 있어.
순서가 예상한 순서가 아냐. 경우에 따라서 줄 안바뀌고 붙어서 출력 되고 있어.
왜 그러냐면 스레드가 구동이 되는 순간 10개 구동 되면 병렬로 실행이 되기 때문에 누가 먼저 출력할지는 예상을 하기 힘들어.
cout이라는 표준 입출력 기능을 이용할 때 멀티 스레드 환경에서 정확하게 예상한대로 하나가 실행되고 다음애가 실행되는 순차적인 순서가 보장이 되지 않아서 섞여서 출력 된다.
동기화 해주기 않았기 때문.
그럼에도 다수 스레드 만들어 구동시켜서 동작 시켜서 기다려주는 부분까지는 실습 해볼 수 있었어.
나중에 가면 HelloThread_2에서 무한 루프 돌면서 DB 작업도 하고 , 누구는 게임 일감을 실행하고, 누구는 Network IOCP 쪽에 가서 해당하는 네트워크 패킷을 처리하고 이런 부분들을 각기 뿌려주게 될거야.
시작은 작게 시작하지만 이런 기능들이 쌓여서 거대한 MMO 서버를 지탱한다.
스레드 구동시켜서 스레드 관리하는 간단한 방법 알아 봤어.
구동하는 거 까지는 알았지만 데이터를 공용으로 관리하는 실습은 해보지 않았기에 반쪽짜리야.
반응형
'Server programming' 카테고리의 다른 글
01_03_멀티쓰레드_컴파일러 최적화 (0) | 2023.03.12 |
---|---|
01_02_멀티쓰레드_쓰레드 생성 (0) | 2023.03.12 |
01_01_멀티쓰레드 개론 (0) | 2023.03.12 |
00_03_OT_환경 설정 (0) | 2023.03.12 |
00_02_OT_서버 개론 (0) | 2023.03.12 |
댓글