서론
이번 강의에서는JobQueue를 c#의 람다식을 사용해서 간편하게 만드는 방법 이전 시간에 배웠던 커맨드 디자인 패턴을 사용해서 만드는 방법 두가지를 만들어 보겠다
그래서 JobQueue가 뭔대?
쉽게 말해서 내가 행동하는 항목들을 Queue로 들고있는걸 JobQueue라 칭함
왜 만듬?
멀티 스레드 방식이다보니 공유자원에 접근할 일이 있다고 가정하면
각 스레드가 어떠한 일을 수행할 때마다 Lock을 걸고 내가 해야할일을 진행했다
수행해야할 일이 끝나기 전 까지는 다른 스레드는 그 공간을 들어 갈 수가없는데
그러면 다른 스레드들은 공간에 들어가기 전까지 기다려야한다
즉, 멀티스레딩을 하고있지만 비효율적으로 하고있는거다 이럴바엔
싱글 스레드를 사용하는것과 다를바 없다
예시
전제 조건 : 어느 방에 유저가 100명있음 일단 수행작업은 BroadCast를 한다는 조건
수행 : 게임 룸 입장 ->
클라 : 데이터 Send() x 100 ->
서버 : 100명의 유저에게BroadCast() x 100
스레드가 BroadCast를 다 마칠 때 까지 다른 스레드는 기다려야함
만약 BroadCast가 길어진다? 그러면 싱글스레드와 다를바 없음
결론
우리는 그래서 JobQueue라는 시스템을 만들어서
BroadCast같이 수행해야하는 작업을 하나의 스레드에게만 일을 시켜서 진행해보자
즉, JobQueue가 아무일 안할 때 처음 접근하는 스레드가 해당 일만 수행하는 역할을
주면 어떨까 라는 생각이다.
이외 다른 스레드는 Queue안에 수행해야할 작업들을 넣기만하고
지금 처리해야하는 스레드는 Queue안에있는 작업만 처리하게끔 하면 보다 깔끔하게
멀티스레딩이 가능할 것 같다
JobQueue 클래스 소개 (Action Delegate활용)
인터페이스
- Push() ⇒ 각 스레드가 수행해야 할 작업을 Enqueue를 하게끔 유도
public interface IJobQueue
{
void Push(Action job);
}클래스 메서드 소개
- Push
- 해야할 작업 Enqueue유도
- 처음 들어온 스레드가 해당 작업을 수행 하게 끔 처리
public void Push(Action job)
{
bool flush = false;
lock (_lock)
{
_jobQeueue.Enqueue(job);
if (_flush == false)
flush = _flush = true;
}
if (flush)
Flush();
}- Flush
- Queue에 일감이있으면 처리해주는 용도
private void Flush()
{
while (true)
{
Action action = Pop();
if (action == null)
return;
action?.Invoke();
}
}- Pop
- 먼저 온 순서대로 저장되어있는 일감을 빼내는 용도
private Action Pop()
{
lock (_lock)
{
if (_jobQeueue.Count == 0)
{
_flush = false;
return null;
}
else
return _jobQeueue.Dequeue();
}
}전체 코드
public class JobQueue : IJobQueue
{
Queue<Action> _jobQeueue = new();
object _lock = new object();
private bool _flush = false;
public void Push(Action job)
{
bool flush = false;
lock (_lock)
{
_jobQeueue.Enqueue(job);
if (_flush == false)
flush = _flush = true;
}
if (flush)
Flush();
}
private void Flush()
{
while (true)
{
Action action = Pop();
if (action == null)
return;
action?.Invoke();
}
}
private Action Pop()
{
lock (_lock)
{
if (_jobQeueue.Count == 0)
{
_flush = false;
return null;
}
else
return _jobQeueue.Dequeue();
}
}
}사용 예시
class GameRoom : IJobQueue
{
private JobQueue _jobQueue = new JobQueue();
public void Push(Action job)
{
_jobQueue.Push(job);
}
public void BroadcastChat(ClientSession session, string chat)
{
S_Chat packet = new S_Chat();
packet.playerId = session.SessionId;
packet.chat = $"[{session.SessionNickName}] : {chat}";
ArraySegment<byte> sagment = packet.Write();
foreach (ClientSession s in _sessions)
s.Send(sagment);
}
}수행해야 할 작업을 요청
class PacketHandler
{
public static void C_ChatHandler(PacketSeesion session, IPacket packet)
{
GameRoom room = clientSession.Room;
room.Push(() =>
{
room.BroadcastChat(clientSession, chatPacket.chat);
});
}
}이외 Lamda식이아닌 클래스로 만들어서 처리 예제
interface ITask
{
public void Execute();
}
class BroadcastTask : ITask
{
GameRoom _room;
ClientSession _session;
string _chat;
public BroadcastTask(GameRoom room, ClientSession session, string chat)
{
_room = room;
_session = session;
_chat = chat;
}
public void Execute()
{
_room.BroadcastChat(_session, _chat);
}
}
class TaskQueue
{
Queue<ITask> _queue = new();
// 여기서 만들어 둔 JobQueue처럼 진행하면 될듯?
}