JobTimer

Eunho Bae·2022년 5월 11일
0

설명

JobTimer라는 중앙시스템을 하나 만들어두고, job들을 일정시간 후에 실행시키도록 관리를 맡기는 식으로 짜는게 효율적이고 Main이 커지는 것을 막을 수 있다. 우선순위 큐를 구현하고 job마다 설정된 시간 순서대로 logn의 시간복잡도 안에 정렬시킨 후 빠르게 때가 된 job들을 실행시키는 방식으로 구현해보자

Server의 Main에서 FlushRoom()이라는 작업을 0초 후에 실행하도록 우선순위 큐에 넣어서 예약하고 있고 무한루프를 돌면서 반복적으로 JobTimer의 Flush()를 실행시키고 있다.
Flush() 함수는 현재 실행이 임박한 작업을 가져와서 현재 시각이 그 작업의 시작 시각을 넘었는지 비교하고 만약 넘었다면 작업을 실행시키고, 그렇지 않으면 그냥 빠져나와 다음 Flush()를 실행시키며 계속 시간을 체크하는 식으로 동작한다.


코드

Server

Program.cs

class Program
	{
		static Listener _listener = new Listener();
		public static GameRoom Room = new GameRoom();

		static void FlushRoom()
		{
			Room.Push(() => Room.Flush());
			JobTimer.Instance.Push(FlushRoom, 250);
		}

		static void Main(string[] args)
		{
			// DNS (Domain Name System)
			string host = Dns.GetHostName();
			IPHostEntry ipHost = Dns.GetHostEntry(host);
			IPAddress ipAddr = ipHost.AddressList[0];
			IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);

			_listener.Init(endPoint, () => { return SessionManager.Instance.Generate(); });
			Console.WriteLine("Listening...");

			//FlushRoom();
			JobTimer.Instance.Push(FlushRoom); // tickAfter = 0

			while (true)
			{
				JobTimer.Instance.Flush();
			}
		}
	}

JobTimer.cs

// Job 예약 시스템
	struct JobTimerElem : IComparable<JobTimerElem>
	{
		public int execTick; // 실행 시간
		public Action action;

		public int CompareTo(JobTimerElem other)
		{
			return other.execTick - execTick;
		}
	}

	class JobTimer
	{
		PriorityQueue<JobTimerElem> _pq = new PriorityQueue<JobTimerElem>();
		object _lock = new object();

		public static JobTimer Instance { get; } = new JobTimer();

		public void Push(Action action, int tickAfter = 0)
		{
			JobTimerElem job;
			job.execTick = System.Environment.TickCount + tickAfter; // 현재시간 + 얼마후
			job.action = action;

			lock (_lock) // _pq는 공용데이터. 다른 쓰레드에서 접근 가능
			{
				_pq.Push(job);
			}
		}

		public void Flush()
		{
			while (true)
			{
				int now = System.Environment.TickCount;

				JobTimerElem job;

				lock (_lock)
				{
					if (_pq.Count == 0)
						break;

					job = _pq.Peek();
					if (job.execTick > now)
						break;

					_pq.Pop();
				}

				job.action.Invoke();
			}
		}
	}

PriorityQueue.cs

public class PriorityQueue<T> where T : IComparable<T>
	{
		List<T> _heap = new List<T>();

		public int Count { get { return _heap.Count; } }

		// O(logN)
		public void Push(T data)
		{
			// 힙의 맨 끝에 새로운 데이터를 삽입한다
			_heap.Add(data);

			int now = _heap.Count - 1;
			// 도장깨기를 시작
			while (now > 0)
			{
				// 도장깨기를 시도
				int next = (now - 1) / 2;
				if (_heap[now].CompareTo(_heap[next]) < 0)
					break; // 실패

				// 두 값을 교체한다
				T temp = _heap[now];
				_heap[now] = _heap[next];
				_heap[next] = temp;

				// 검사 위치를 이동한다
				now = next;
			}
		}

		// O(logN)
		public T Pop()
		{
			// 반환할 데이터를 따로 저장
			T ret = _heap[0];

			// 마지막 데이터를 루트로 이동한다
			int lastIndex = _heap.Count - 1;
			_heap[0] = _heap[lastIndex];
			_heap.RemoveAt(lastIndex);
			lastIndex--;

			// 역으로 내려가는 도장깨기 시작
			int now = 0;
			while (true)
			{
				int left = 2 * now + 1;
				int right = 2 * now + 2;

				int next = now;
				// 왼쪽값이 현재값보다 크면, 왼쪽으로 이동
				if (left <= lastIndex && _heap[next].CompareTo(_heap[left]) < 0)
					next = left;
				// 오른값이 현재값(왼쪽 이동 포함)보다 크면, 오른쪽으로 이동
				if (right <= lastIndex && _heap[next].CompareTo(_heap[right]) < 0)
					next = right;

				// 왼쪽/오른쪽 모두 현재값보다 작으면 종료
				if (next == now)
					break;

				// 두 값을 교체한다
				T temp = _heap[now];
				_heap[now] = _heap[next];
				_heap[next] = temp;
				// 검사 위치를 이동한다
				now = next;
			}

			return ret;
		}

		public T Peek()
		{
			if (_heap.Count == 0)
				return default(T);
			return _heap[0];
		}
	}
profile
개인 공부 정리

0개의 댓글

관련 채용 정보