공부/인프런 - Rookiss

Part 4-5-4. Job Queue : Job Queue #1

셩잇님 2023. 12. 27. 15:45
반응형

 

 

Job Queue

 

 지난 시간에는 디자인 패턴 중 커맨드 패턴에 대해서 학습하는 시간을 가지었다. 커맨드 패턴은 사용자의 요구사항을 객체로 캡슐화하여 일감을 처리하는 스레드에게 던져준다고 이해하면 될 것같다. 오늘은 학습한 커맨드 패턴을 활용하여 Job Queue를 마저 구현해보자.

 


 

🧱 JobQueue

 

 우선 Server 영역에 JobQueue 라는 클래스를 새롭게 만들어준다. 해당 클래스를 다른 곳에서도 사용하기 위하여 Public 으로 변경해주고, IJobQueue 라는 Interface를 만들어 해당 인터페이스를 상속 받도록 설정해준다.

 

📏 Interface

 

 일감을 밀어 넣기 위한 함수 Push()를 새롭게 만들어주고, 이 행위 자체를 넘겨주기 위해 Action을 매개변수로 이용하여 사용한다.

 

 

📏 JobQueue Class

 

 만들어두었던 IJobQueue 인터페이스를 상속받도록 설정해준다. 또한 어떠한 Action인지를 저장 및 관리하기 위한 Action 타입의 Queue를 선언하고, 멀티쓰레드를 대응하기 위해 lock 또한 선언하여 사용해준다.

 

 

 이 후, 일감을 저장하기 위한 Push 메서드와, 일감을 사용하기 위해 빼내는 Pop 메서드를 위와 같이 작성하여 준다.

 

📏 GameRoom

 

 이제 저장하고 저장한 일을 과연 누가 처리할것인가?에 대한 과정이 필요하다. 이러한 과정은 GameRoom에서 처리 할 것이다. GameRoom 클래스 또한 IJobQueue를 상속 받고, JobQueue를 인스턴스 해준 뒤에 GameRoom 클래스에도 Push 메서드르 만들어 JobQueue의 Push가 동작하도록 설정한다.

 

 

 이제 기존에 GameRoom을 통해 Enter, Leave, BroadCast 했던 부분들을 바로 실행시키는 것이 아닌 JobQueue에 담아주는 형태로 변경하여 준다.

 


 

🛀 Enter, Leave

 

 클라이언트 세션에서 작성해주었던 함수들을 아래와 같이 수정하여 준다.

 

 

🛀 BroadCast

 

 PacketHandler에 작성해주었던 함수를 아래와 같이 수정하여 준다.

 

 

 위와 같이 수정해주었다면 해당 일감을 처리하기 위해 다시 JobQueu로 넘어가자.

 


 

📏 Flush

 

 커맨드 패턴을 활용하기 위해서는 처음에 일감을 들고 온 쓰레드를 일감을 처리하는 스레드로 설정할 것이다. 왜냐하면 일감을 던져주려고 왔는데, 일감을 처리할 쓰레드가 없기 때문에 본인이 이제 도맡아서 처리해야 하기 때문이다. 따라서 Push가 진행 될 때에 처음으로 들어온 것인지, 아닌지를 파악하는 과정이 필요하다. 이를 파악하기 위해서 _flush, flush 두 개를 이용하여 이중 bool 구조로 구성한다.

 

왜 두 개의 Bool을 사용해야 할까?
 만약 _flush 하나만 사용한다면 멀티쓰레드 환경에서 Flush 메서드를 처리하고 있는 순간에도 _flush가 true의 값을 가지기 때문에 다른 쓰레드들이 계속 접근할 수 있다. 따라서 이 부분에 제한을 두기 위해 이미 일감을 처리하는 쓰레드가 있다면 다른 쓰레드들은 Push만 할 수 있도록 이중 Bool을 이용하여 구성하는 것이다.

 

 

 또한 Flush 메서드는 안에 일감이 있다면 계속 동작하는 구조로써, 여기에서 Flush 혼자 들어왔는데 궂이 Pop()에서 lock을 이용해야 할까? 생각이 들지만, 마찬가지로 Pop을 하는 순간에도 Push가 동작할 수 있기 때문에 이러한 충돌 방지를 위해 lock을 걸어줄 필요성이 있다.

 

 

 이러한 구조로 일감이 없을 때까지 빼내어 처리(Invoke)해주는 Flush 메서드를 마지막으로 JobQueue에서는 해줄 것들을 다 해주었다.

 


 

⚾ No Lock

 

 보다 더 개선을 해보자면, 일감 처리(Enter, Leave, Broadcast)를 다 Flush 라는 상호배제된 영역에서 진행하기 때문에 GameRoom 에서는 더 이상 lock 처리가 필요하지 않다. 따라서 이를 모두 제거하여 사용하도록 한다.

 

 


 

실행결과 😎

 

 이제 테스트를 위해 프로젝트를 실행해보고, 브레이크 포인트를 걸어 확인해본다면 지난 시간에 비해 스레드가 굉장히 줄어든 것을 확인할 수 있다.

 

 

 다만, 실행하고 있는 와중에 Client를 종료해 꺼버린다면 아래 BroadCast에서 Null 크러시가 발생하는데, 이는 JobQueue 방식을 통해 일감을 담아두고 사용하기 때문에 Room에 접근하는 순간에 Room이 이미 Null이 되어서 오류가 발생하는 것이다.

 

 

 따라서 이를 해결하기 위해서 간단하게 GameRoom 타입의 변수 room을 생성해 여기에 client.Room의 참조 주소 값을 두고 저장해서 사용하면 된다.

 

 

 마찬가지로 Push, Leave 하는 곳에서도 위와 같이 코드를 수정하면 Null 레퍼런스 크러쉬 오류가 나타나는 것이 없어진 것을 볼 수 있다.

 

 

반응형