Server programming

04_04_JobQueue_JobQuene#1

devRiripong 2023. 5. 7.
반응형

요약:

1. JobQueue 클래스 파일을 ServerCore에 생성하고, IJobQueue 인터페이스를 선언한다. JobQueue 클래스는 Action을 Queue로 들고 있으며, Push 함수와 Pop 함수를 정의한다.
2. GameRoom에 JobQueue를 적용하고 IJobQueue 인터페이스를 상속받아서 Push 인터페이스를 구현한다.
3. GameRoom의 Broadcast, Enter, Leave를 호출할 때 직접 접근이 아닌 _jobQueue에 밀어 넣는 방식으로 수정한다.
4. JobQueue에서 Push를 할 때 첫번째 일감이면 실행까지 담당하고, 첫번째가 아니라면 큐에 밀어넣고 바로 빠져나오는 방식을 구현한다.
5. GameRoom에서 JobQueue 인터페이스를 사용하므로, Broadcast, Enter, Leave 함수들 안의 락을 제거한다.
6. 실행 시, 스레드가 줄어든 것을 확인한다.
7. DummyClient를 끄면 생기는 오류를 수정하고, Room을 없애는 부분을 수정한다. 이를 통해 Client를 종료해도 크래시가 발생하지 않는 것을 확인한다.:

 

 

1.ServerCore에 JobQueue 클래스 파일을 만듭니다. 

  1.1. IJobQueue라는 인터페이스를 선언

    1.1.1. Push를 선언

  1.2. JobQueue 클래스

    1.2.1. 안에 Action을 Queue로 들고 있는 jobQueue 생성

    1.2.2. 락을 위한 오브젝트 _lock 생성

    1.2.3. IJobQueue를 상속받음

    1.2.4. Push 함수 정의.

      1.2.4.1. public 붙임

      1.2.4.2. lock을 걸고

      1.2.4.3. 매개 변수로 받은 Acction job을 _jobQueue에 Enqueue로 밀어 넣음.

    1.2.5. Pop 함수 정의

      1.2.5.1. public 굳이 안붙임

      1.2.5.2. lock을 걸고 

      1.2.5.3. ._jobQueue.Count가 null이면 null return, 아니면 _jobQueue.Dequeue로 맨 마지막거 return

using System;
using System.Collections.Generic;
using System.Text;

namespace ServerCore
{
    public interface IJobQueue
    {
        void Push(Action job); 
    }

    public class JobQueue : IJobQueue
    {
        Queue<Action> _jobQueue = new Queue<Action>();
        object _lock = new object();

        public void Push(Action job)
        {
            lock(_lock)
            {
                _jobQueue.Enqueue(job); 
            }
        }

        Action Pop()
        {
            lock(_lock)
            {
                if(_jobQueue.Count == 0)
                {
                    return null; 
                }
                return _jobQueue.Dequeue();
            }
        }
    }
}

 

2. Server의 GameRoom에다가 JobQueue를 적용

  2.1. Server의 GameRoom

    2.1.1. IJobQueue라는 인터페이스를 상속

    2.1.2. using ServerCore 추가

    2.1.3. JobQueue _jobQueue 생성

    2.1.4. Push 인터페이스 구현. _jobQueue에 매개 변수로 받은 Action job 넣음

using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace Server
{
    class GameRoom : IJobQueue
    {
        List<ClientSession> _sessions = new List<ClientSession>();
        object _lock = new object();
        JobQueue _jobQueue = new JobQueue(); 

        public void Push(Action job)
        {
            _jobQueue.Push(job);
        }

 

3. Server의 GameRoom의 Boradcast, Enter, Leave를 호출할 때 직접 접근이 아닌 _jobQueue에 밀어 넣는 방식으로 수정

  3.1. PacketHandler의 Broadcast 

    3.1.1. clientSession.Room.Push( () => clientSession.Room.Broadcast(clientSession, chatPacket.chat));

    3.1.2. clientSession.Room이 null이 되면 문제가 됨. 후에 고침.

  3.2. ClientSession의 Enter, Leave

    3.2.1. Program.Room.Push(() => Program.Room.Enter(this));

    3.2.2. Room.Push(() => Room.Leave(this));

    3.2.3. Room.Push에서 Room이 null이 되면 문제가 됨. 후에 고침.

 

4. JobQueue에서 Push를 할 때 만약에 맨 처음으로 잡큐 안에다가 일감을 밀어 넣는 거면 실행까지 담당하고, 첫번째가 아니라면 큐 에다가 밀어넣고 바로 빠져나오는 방식 구현

  4.1. JobQueue의 Push를 하며 판별

    4.1.1. JobQueue 클래스에 bool _flush 선언 - false면 _flush와 해당 스레드의 변수 flush가 true가 되면서 직접 처리

    4.1.2. Push 함수 안 스택 영역에 bool flush 선언 - _flush가 false면 flush를 true로 하고 이걸로 Flush 실행 여부 판단

    _flush는 힙 메모리에 올라가는 변수라 모든 쓰레드가 동일하게 사용

    만약 if(flush)가 아니라 if(_flush)로 Flush()의 실행여부를 판정하게 된다면,

    모든 쓰레드들이 if (_flush) 조건에 걸려 Flush()를 하게 될 거다.

    반면 스택에 위치한 bool flush 는 딱 실행중인 쓰레드만 볼 수 있는 데이터라서 if(flush)를 하면

    우리가 원하는 [한명만 실행하는] 사양을 구현할 수 있게 된다.

  4.2. Flush 기능 구현

    4.2.1. while(true) 무한 돌면서 Pop으로 _jobQueue에서 Dequeue를 해 Action action에 넣는다.

    4.2.2. action이 null 이면 return, 아니면 Invoke 한다.

    4.2.3. Flush는 혼자 하는 건데 왜 Pop에 lock을 걸었나? Push 동시 다발적으로 할 때 _jobQueue 보호 위해

  4.3. 모든 일감 처리 끝났으면 Flush가 끝났다는 상태 관리

    4.3.1. Pop에서 _jobQueue.Count가 0이면 _flush=false를 한다. 

  

5. GameRoom에서  JobQueue 인터페이스를 사용한다는 건 한번에 하나의 쓰레드만 Broadcast, Enter, Leave를 사용한다는 말이니까 Broadcast, Enter, Leave 함수들 안의 락을 제거해 준다.

 

6. 실행

  6.1. C_ChatHandler에서 clientSession.Room.Push 부분에 breakPoint를 잡고 실행을 해본다.

  6.2. 스레드가 이전에 비해 줄어 든 것을 확인할 수 있다.

  

7. DummyClient를 끄면 생기는 오류 수정

  7.1. DummyClient를 끄면 C_ChatHandler의 Push 부분에서 clientSession.Room 이 null이 되면서 크래시가 난다.

  7.2. 원인은 Client를 끄면 clientSession의 Room에서 OnDisconnected가 실행되는데 이 때 GameRoom Room에 null을 대입하는데 뒤늦게 예약된 Room의 BroadCast를 실행하라고 Invoke를 할 때 Room에 null이 들어가 있기 때문에 크러시가 나는 것이다.

  7.3. Room을 없애는 부분을 수정한다.

    7.3.1. clientSesison에서 내가 들고 있는 Room은 언제든지 바뀔 수 있으니까 ClientSession의 OnDiconnected에 가서 GameRoom room = Room 이렇게 추출해 참조를 유지하며 사용한다.

    7.3.2. 마찬가지로 PacketHandler의 C_ChatHandler에 가서 GameRoom room = clientSession.Room; 이렇게 추출해 사용한다.

    7.3.3. OnConnected에서는 Program의 전역 Room이었기에 수정 안해도 된다.

    7.3.4. 실행을 해보면 Client를 꺼도 크래시가 안나는 것을 볼 수 있다.

.

 

출처: https://inf.run/q3dD

반응형

댓글