Chat 기능을 제작하며 어떤 식으로 구조가 돌아가는지 알 수 있었다.
다만 아래와 같은 악순환구조가 발생하였다. 이번에는 이를 개선하고자 한다.
원래 이 내용을 따로 정리를 하고자하여 글을 작성하지 않았으나 위에서 명시한 문제점을 해결할 때 이해에 도움이 되니 간단하게 적어두면
지금은 일감을 받은 쓰레드가 일감 처리까지 모두 하고 있는데 lock의 특성으로 한 번에 하나의 쓰레드만 움직이다 보니 대기하는 쓰레드들이 늘었다.
그래서 다른 쓰레드들은 일감을 저장만 해두고, 일 처리하는 담당을 두어 작업을 시키는 방식으로 진행할 것이다.
실제 구현은 저렇게 해도 되지만, 이번에는 첫 번째로 들어간 쓰레드가 일 처리 전문 쓰레드가 되는 방식으로 구현할 것이다
우선 ServerCore 영역에 JobQueue 클래스를 새로 생성해준다. 다른 곳에서도 사용하기 위해 Public으로 해주고 IJobQueue라는 interface를 만들어 인터페이스를 상속 받도록 설정해준다..
일감을 밀어 넣기 위한 Push()
함수를 생성해주고, 그 행위 자체를 넘겨주기 위해 Action 을 매개변수로 사용한다.
IJobQueue 인터페이스를 상속 받도록 설정해준다.
어떤 Action인지 저장해두기 위한 Action타입의 Queue를 선언, 멀티쓰레드에 대비한 lock도 선언해준다.
일감을 저장할 때를 위한 Push()
, 일감을 빼낼 때를 위한 Pop()
을 생성해준다.
이제 저장을 하고, 저장한 일을 누가 처리할거냐의 과정이 필요하다. 이 과정을 GameRoom 에서 처리 할 것이다.
GameRoom도 IJobQueue 상속 받아 주고, JobQueue를 인스턴스 해준 뒤 여기도 Push함수를 만들어 JobQueue의 Push()가 동작하게 한다.
이제 기존에 GameRoom 을 통해 Enter, Leave, BroadCast 했던 부분들. 즉, 일감들을 바로 실행시키는게 아닌 JobQueue에 담아두는 과정으로 변경해준다.
이제 다음은 그 일감들을 처리 작업하기 위한 함수가 필요하여 다시 IJobQueue로 넘어간다.
위에서 계획한 대로 처음에 일감을 들고온 애가 일감을 모두 처리하고 나가는 방식으로 진행 될 것이니까 Push 할 때 처음들어 온건지 아닌지 검사하는 과정이 필요하다.
여기서는 _flush, flish 두 개를 사용해 이중 bool 구조로 구성하였다.
왜 Bool 두개를 사용했는가?
만약 _flush 하나만 사용한다면 멀티쓰레드 이기에Flush()
를 처리하고 있는 순간에도 _flush가 true이라서 다른 애들도 계속 접근할 수 있다.
이러한 부분에 제한을 두면서 이미 일 처리하는 애가 있다면 다른 애들은 Push만 하도록 이중 bool로 구성을 한 것
또한 Flush()
는 안에 일감이 있으면 계속 동작하는 구조로 여기서 Flush()는 혼자 들어왔는데 굳이 Pop()에서 lock을 걸어줄 필요가 있을까? 생각이 들지만
Pop하는 순간에도 Push를 할 수가 있기에 충돌 방지를 위해 lock을 걸어준것이다.
이러한 구조로 일감이 없을 때 까지 빼내어 처리(Invoke())해주는 Flush로 구성해주면 전반적으로 마무리가 되었다.
이제 조금더 개선해보자면, 일감 처리(Enter, Leave, BroadCast)를 다 Flush라는 상호배제된 임계영역에서 진행하기에 GameRoom에서는 lock 처리를 더이상 해주지 않아도 되어 다 제거해준다.
이제 테스트를 해보고 스레드를 보면 지난 번에 비해 스레드 수가 굉장히 줄어든 걸 확인 할 수 있다.
다만 실행되고 있을 때 DummyClient를 꺼버리면 사진 속 PacketHandler의 client.Room~ 부분에서 Null Crash가 발생하는데
JobQueue 방식을 통해 일감을 담아두고 사용하기 때문에 Room에 접근하는 순간 Room 자체가 이미 null 이어서 오류가 발생하는 것이다.
이를 해결 하기 위해서 간단하게 GameRoom 타입의 room 변수를 생성해 여기에 client.Room의 참조 주소 값을 저장해두고 사용하는 것이다.
그 Room을 null로 만드는 곳이 이곳인데 여기도 같이 room 변수에 참조주소 값을 복사 해두면 Room이 null이 되더라도 코드가 잘 동작하게 된다.
이렇게 해주면 Client를 종료 하더라도 Null Crush는 발생하지 않는다.