잡큐를 배우기 전 COMMAND 패턴을 배워보자 굉장히 중요하다고 한다.
디자인 패턴에서 커맨드 패턴이 매우 유용하다.
식당에서 손님이 통화를 하고있다 하자.
일반적인 식당에서 주문을 한다 하자. 그러면 주문을 받은 사람이 요리를 한다.
우리는 지금까지 세션이 요청을 받은 애가 실행까지 해줬다. 작은 규모면 괜찮은데 만약 클라이언트가 많아져 주방의 경합이 심해지면 주방의 Lock을 대기하느라 다들 아무것도 안하고 있을 것이다.
만약 실제 큰 식당이라면 일처리를 이렇게 할것이다.
주방장이 따로 있고, 서빙직원이 따로있다. 주문 받는 직원은 주문서만 받아서 주방장에게 줄것이다.
이 방식의 장점은 크게 두가지
1. 주문서를 만들어서 주방장에게 던지기때문에 각자 할 일에 집중할 수 있음.
2. 주문서를 만드는 시점과 요리를 하는 시점이 분리가 되어서 둘로 나뉠 수 있다. 만약 요리가 많이 밀렸다면 주문서 순서에 따라 한참 뒤에 실행해줄것이다. 그리고 주문서를 넣고 갑자기 고객이 취소하면, 요리가 들어가지 않았다면 취소도 가능할 것이다.
커맨드패턴은 결국 어떤 요청을 캡슐화(클래스나 어떤객체)해서 건네준다는게 핵심이다.
이렇게 하면 lock을 잡고 다같이 경합하는게 아니라 주문서를 만들어 넣고 던지고 바로 빠져나와서 자기가 할 일을 할 수 있다는 장점이 있다.
이제부터는 락을 잡고 일을 다 하는게 아니라 일감만 던지도록 만들것이다.
이 컨셉을 간략하게 표현하면 아래와 같다.
class IJob
{
public:
virtual void Execute() {}
};
class HealJob : public IJob
{
public:
virtual void Execute() override
{
// target을 찾아서 힐
cout << _target << "에게 힐을" << _healValue << " 만큼 줌" << endl;
}
public:
uint64 _target = 0;
uint32 _healValue = 0;
};
int main()
{
//Test Job
{
// 일감 의뢰 내용 : 1번 유저한테 10만큼 힐을 줘라!
//행동 : Heal
// 인자 : 1번 유저, 10이라는 힐량
HealJob healJob;
healJob._target = 1;
healJob._healValue = 10;
// 이 Job을 등록해놓고 나~중에 실행됨
healJob.Execute();
}
}
이 방법의 단점은 일감을(종류) 만들때마다 클래스를 하나씩 만들어줘야 하는게 끔찍하게 느껴진다.
그런데 놀랍게도 이렇게 만드는 프로젝트가 꽤 많다고 한다. 일감이 필요할때마다 클래스를 하나씩 늘려주는것.
(Template 흑마법을 이용하면 타개할 수 있긴 한듯)
일단은 위와같이 하나하나 클래스를 늘려가며 만들어보자.
이제 Room에 JobQueue를 적용시켜보자.
#pragma once
#include "Job.h"
class Room
{
friend class EnterJob;
friend class LeaveJob;
friend class BroadcastJob;
private:
// 싱글쓰레드 환경처럼 코딩
void Enter(PlayerRef player);
void Leave(PlayerRef player);
void Broadcast(SendBufferRef sendBuffer);
public:
// 멀티쓰레드 환경에서는 일감으로 접근
void PushJob(JobRef job) { _jobs.Push(job); }
void FlushJob();
private:
map<uint64, PlayerRef> _players;
JobQueue _jobs;
};
extern Room GRoom;
class EnterJob : public IJob
{
public:
EnterJob(Room& room, PlayerRef player) : _room(room), _player(player)
{
}
virtual void Execute() override
{
_room.Enter(_player);
}
public:
Room& _room;
PlayerRef _player;
};
class LeaveJob : public IJob
{
public:
LeaveJob(Room& room, PlayerRef player) : _room(room), _player(player)
{
}
virtual void Execute() override
{
_room.Leave(_player);
}
public:
Room& _room;
PlayerRef _player;
};
class BroadcastJob : public IJob
{
public:
BroadcastJob(Room& room, SendBufferRef sendBuffer) : _room(room), _sendBuffer(sendBuffer)
{
}
virtual void Execute() override
{
_room.Broadcast(_sendBuffer);
}
public:
Room& _room;
SendBufferRef _sendBuffer;
};
이제 Room의 Enter Leave Broadcast에서는 Lock을 걸지 않고 마치 싱글스레드에서 처리하듯이 한다.
위 세 함수는 이제 job을 처리하는 부분에서만 실행될것이므로 private 함수로 로 변경하고 friend로 job클래스에서만 실행할 수 있게 만들어준다.
bool Handle_C_ENTER_GAME(PacketSessionRef& session, Protocol::C_ENTER_GAME& pkt)
{
GameSessionRef gameSession = static_pointer_cast<GameSession>(session);
//TODO 유효성 검사
uint64 index = pkt.playerindex();
PlayerRef player = gameSession->_players[index];// read only?
//GRoom.Enter(player); @@@@@@@@@ 이 부분 변경@@@@@@@@
GRoom.PushJob(MakeShared<EnterJob>(GRoom, player)); // 일감만 예약해줌
// 이 아랫부분을 실행해주는게 좀 이상하지만 일단은 놔둠
Protocol::S_ENTER_GAME enterGamePkt;
enterGamePkt.set_success(true);
auto sendBuffer = ClientPacketHandler::MakeSendBuffer(enterGamePkt);
player->ownerSession->Send(sendBuffer);
return true;
}
bool Handle_C_CHAT(PacketSessionRef& session, Protocol::C_CHAT& pkt)
{
cout << pkt.msg() << endl;
Protocol::S_CHAT chatPkt;
chatPkt.set_msg(pkt.msg());
auto sendBuffer = ClientPacketHandler::MakeSendBuffer(chatPkt);
//GRoom.Broadcast(sendBuffer); @@@@@@@@@ 이 부분 변경 @@@@@@@@
GRoom.PushJob(MakeShared<BroadcastJob>(GRoom, sendBuffer));
return true;
}
일단 잡큐에 일감을 넣고는 있는데 누군가 그걸 처리해줘야 할것이다.
일단은 단순하게 main 스레드에서 하게 만들어보자.
void Room::FlushJob()
{
while (true)
{
JobRef job = _jobs.Pop();
if (job == nullptr)
break;
job->Execute();
}
}
main()
{
/*...*/
while (true)
{
GRoom.FlushJob();
this_thread::sleep_for(1ms);
}
GThreadManager->Join();
}
이전과 다르게 JobQueue에 Job을 Push, Pop하는 잠깐동안만 Lock을 걸고, Broadcast와 같이 작업시간이 오래 걸리는건 main 스레드에서 Lock없이 단독으로 처리해주고 있기 때문에 동일 작업에 대한 Lock때문에 오랫동안 워커쓰레드들이 대기하는 현상이 줄어든다.
만약 FlushJob을 여러 쓰레드가 사용하게 된다면 문제가 생기고, 또다시 락을 걸어야하는 상황이 생기므로 반드시 하나의 쓰레드만 실행해야 한다.
'강의 수강 > 게임서버(1)' 카테고리의 다른 글
| 72. JobQueue #3 (0) | 2025.08.22 |
|---|---|
| 71. JobQueue #2 (0) | 2025.08.19 |
| 69. 채팅 실습 (0) | 2025.08.18 |
| 68. 패킷 자동화 #2 (0) | 2025.08.18 |
| 67. 패킷 자동화 #1 (0) | 2025.08.17 |