네트워크, 서버

JobQueue

춤추는수달 2022. 7. 16. 19:07

JobQueue 란?

JobQueue는 처리해야 할 일들을 작업(Job/Task)이라는 단위로 만들고, 그 작업들을 대기열(Queue)에 넣고, 그 대기열에서 작업들을 꺼내서 처리하는 방식을 말한다. 쉽게 말하면 어떤 해야할 일이 생겼을 때, 그 일을 바로 해버리는 것이 아니라. 일 목록을 만들어 적어놨다가 나중에 목록에 적힌 일들을 처리하는 것이다. 

JobQueue


왜 그렇게 할까?

JobQueue를 사용하는 이유는 어떤 작업이 생겼을 때, 그 작업을 기록해놨다가 다른 사람에게 떠넘길 수 있기 때문이다. 이는 멀티쓰레드 환경에서 엄청난 장점으로 작용할 수 있다. 아래 상황을 상상해보자.


JobQueue가 없는 상황

어떤 식당이 있다. 그 식당에는 여러명의 유능한 직원이 있다. 각 직원들은 요리부터 서빙 그리고 카운터 보기까지 모든 식당 업무에 통달했다. 이 식당이 손님의 주문을 처리하는 과정은 다음과 같다.

  1. 손님이 음식을 주문함.
  2. 직원 한 명이 주문을 받자마자 주방에 가서 요리를 함.
  3. 다른 직원이 이미 화구를 쓰고 있다면 대기함.
  4. 요리가 끝나면 해당 직원이 손님에게 서빙함.

그런데 이 식당은 비극적이게도 불을 쓰기 위한 화구(도구)가 단 1개 밖에 없다. 그러면 식당엔 어떤 상황이 펼쳐질까?

아마도 아래 그림처럼 하나의 화구를 쓰기 위해 여러 직원이 대기하느라 극심한 인력낭비가 발생할 것이다.

식당을 '프로그램'으로 보고, 각 직원을 '쓰레드', 화구를 '공유자원', 주문을 '공유 자원에 장시간 Lock을 거는 작업'이라고 생각해보자. 쓰레드들이 하나의 공유자원에 접근하기 위해 계속 대기하고 있는 끔찍한 상황이다.

JobQueue가 없다면

그렇다면 여기에 JobQueue 방식을 도입해보면 어떨까?


JobQueue가 있는 상황

이제 이 식당에 JobQueue를 도입해보자. 즉, 주문서를 작성하여 주문서 대기열에 걸어놓는 방식을 채택해보자. 그에 따라 식당의 주문 처리과정은 다음과 같이 바뀐다.

  1. 손님이 음식을 주문함.
  2. 직원이 주문을 받으면 주문서를 작성해 도구 대기열에 작업을 추가한다.
  3. 모든 직원은 도구 대기열을 수시로 확인한다.
  4. 만약 대기열에 작업이 들어와 있는데 도구를 아무도 쓰고 있지 않다면 도구를 사용해 도구 대기열의 모든 요리를 만든다. (이하 '점유한다' 라고 표현)
  5. 만약 도구를 누군가 점유하고 있다면 다른 업무를 처리하러 간다.(혹은 쉬러간다.)

이렇게 되면 이제 요리하기 위해 대기하는 직원도 없어지고, 극심한 인력 낭비도 발생하지 않을 것이다.

 이 화구 대기열이 바로 JobQueue역할이다.

이렇게 JobQueue를 도입함으로 써 더이상 대기하는 쓰레드도 없고 모든 쓰레드가 열심히 낭비없이 돌아가게 된 것 같다.

그러나 이런 방식으로는 아직 문제가 발생할 여지가 남아있다.


하나의 쓰레드가 여러개의 JobQueue를 점유하는 현상

더 나아가면 이런 문제도 발생하게 된다. 다음 상황을 상상해보자.

  • 식당에는 화구뿐만 아니라 도 단 1개 밖에 없다. 
  • 식당의 메뉴는 화구와 칼이 모두 필요한 '스테이크'가 있다.
  • '스테이크'는 먼저 칼을 사용해 고기에 칼집을 내고, 화구을 사용해 고기를 굽는 2단계 프로세스를 거친다.
  • '스테이크' 요리 시 굽는 작업과 칼집을 내는 작업은 한 직원이 한 번에 한 가지 만 할 수 있다. 다시 말해, 스테이크를 구우면서 동시에 칼집을 낼 수는 없다.

즉 JobQueue가 여러 개로 늘어났다. 그리고 한 개의 작업이 여러 개의 JobQueue에 걸친 작업으로 이어진다. 실제 프로그램에서도 JobQueue가 단 한 개만 사용될 일은 없고, JobQueue에서 꺼낸 어떤 작업에 의해서 또 다른 작업이 JobQueue에 들어갈 수 있다.

이러한 상황에서 다음과 같은 일이 벌어졌다.

  • 어느 날 스테이크 주문이 들어왔다. 그래서 스테이크 주문을 넣으러 주방에 온 직원 A는 아무도 칼을 점유하지 않은 것을 확인하고 스테이크에 칼집을 내기 위해 칼을 점유했다.
  • 직원 A가 첫 번째 스테이크에 칼집을 내기 위해 칼을 사용하던 중, 직원 B가 스테이크 주문이 추가로 100개가 들어왔다며 일감을 잔뜩 던져주고 갔다.
  • 직원 A는 첫 번째 스테이크에 칼집을 다 내고, 아무도 화구를 점유하지 않은 것을 확인한 후 첫 번째 스테이크를 굽기 위해 화구를 점유했다.
  • 이제 직원 A는 혼자서 화구와 칼을 모두 점유했다.
  • 그리고 직원 A는 혼자서 스테이크 100개를 만들 때 까지 썰고 굽기를 반복했다...

직원 한 명이 여러 JobQueue 점유

참으로 안타까운 사연이다... 한 쓰레드가 여러 개의 JobQueue를 점유 해버려서 다른 쓰레드는 Job을 배정받지 못하고 낭비되어버린 것이다.

만약 직원 한 명이 하나의 도구만 점유했더라면. 직원 A는 칼을 맡고, 직원 B는 화구를 맡아서 분업했다면. 시간은 반 정도 밖에 걸리지 않았을 것이다. 그렇다. 한 직원이 하나의 도구만 점유하도록 하면 분업은 자동적으로 이루어진다.

그럼 직원마다 자신이 이미 도구를 점유중이라면 다른 도구를 점유하지 않도록 해야한다. 따라서 식당의 주문 처리과정은 다음과 같이 바뀐다.

  1. 손님이 음식을 주문함.
  2. 직원이 주문을 받으면 주문서를 작성해 적절한 도구 대기열에 추가한다.
  3. 모든 직원은 모든 도구 대기열을 수시로 확인한다.
  4. 만약 도구 대기열에 작업이 있고, 자신이 아무 도구도 점유하고 있지 않고, 주문에 필요한 도구를 아무도 점유하지 않았다면, 해당 도구를 점유한다.
  5. 만약 그렇지 않다면 다른 업무를 처리하러 간다.(혹은 쉬러간다)


Global Job Queue

바로 위의 주제에서 JobQueue가 여러 개로 늘어났다(화구, 칼) .  식당에 비유된 상황처럼 식당 직원이라면 한 눈에 화구 대기열과 칼 대기열을 볼 수 있겠지만, 우리 CPU친구는 그렇지 않다. 어떤 정보를 확인하고 싶으면 해당 데이터의 메모리 상의 위치를 알아야만 한다. 즉 모든 JobQueue들의 일관된 처리를 위해서는 JobQueue들을 한 데 모아서 관리해줄 필요가 있다.

그것이 바로 JobQueue 들의 Queue, Global JobQueue 이다. (물론 이름이 꼭 Global JobQueue일 필요는 없다.)

JobQueue는 보통 게임 내의 유저, 몬스터 등의 오브젝트마다 한 개씩 있어야 한다고 볼 수 있다. 이런 JobQueue에 Job이 처음 생길 때마다 JobQueue를 Global JobQueue에 넣고, 쓰레드들은 루프를 돌며 Global JobQueue에 JobQueue를 꺼내서 Job을 수행하도록 한다.

그림으로 표현하면 아래와 같을 것이다.


하나의 쓰레드에 일이 너무 몰리는 현상

위의 JobQueue 방식으로는 아직 한계가 있다. 다음 상황을 상상해보자.

만약 쓰레드 수에 비해 처리해야 할 JobQueue 갯수가 훨씬 많다면? 그리고 그 JobQueue들에 Job들이 매우 빠른 속도로 채워지고 있다면? 그런데 그 모든 JobQueue들이 균일하게 처리되어야 한다면??

위의 방식대로라면 모든 쓰레드들이 JobQueue를 하나 씩 점유한 뒤, 점유된 몇 개의 JobQueue들만 빠른 속도로 처리될 것이다. 그리고 나머지 JobQueue들은 아무런 관심도 못 받은 채 방치될 것이다. 

이러한 문제를 해결하기 위해서는 단순히 한 쓰레드가 JobQueue를 점유했던 시간을 체크해서, 일정 시간 이상 점유했을 시 강제로 점유를 해제시키는 방법이 있다. 


참고자료: https://www.inflearn.com/course/%EC%96%B8%EB%A6%AC%EC%96%BC-3d-mmorpg-4/dashboard

 

[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 - 인프런 | 강의

네트워크/멀티쓰레드/운영체제 등 핵심 전공 지식을 공부하고 게임 서버를 바닥부터 만들어보면서 MMORPG 기술을 학습하는 강의입니다. 신입 서버 프로그래머가 알아야 전반적인 지식을 모두 훑

www.inflearn.com

 

'네트워크, 서버' 카테고리의 다른 글

소켓 프로그래밍  (0) 2022.11.02
JobTimer  (0) 2022.07.19
Google Protocol Buffer  (0) 2022.07.07
Completion Port 모델  (0) 2022.04.30
Overlapped 모델(콜백함수)  (0) 2022.04.30