원래 전문가를 위한 파이썬에 있던 예제이고 취향에 따라 조금 수정되었습니다

코루틴들과 이를 돌리는 이벤트 루프가 어떻게 상호작용하는지를 맛볼 수 있는 것 같네요


이벤트 루프에 이벤트들에 적용시킬 루프 타임이 존재합니다

(실제 시간이 아닌 스텝)

이벤트 루프가 우선순위 큐에서 이벤트 타임 기준으로 이벤트를 꺼낸 후

이 이벤트를 실행합니다

그리고 이 이벤트를 보내왔던 코루틴에 다음 이벤트 타임을 계산하여 send하면

코루틴이 이 이벤트 타임이 적용된 다음 이벤트를 yield하고

이벤트 루프가 이 이벤트를 우선순위 큐에 넣는 것이 핵심입니다

(참고로 밑에 EventType의 interval은 그대로 적용되는게 아니라 이를 이용하여 재계산됩니다)


-- 소스


import collections

import queue

import random

import time


EventType = collections.namedtuple('EventType', 'priority message interval')


event_types = {

    'start': EventType(1, '차고 출발', 10),

    'pick': EventType(2, '승객 탑승', 20),

    'drop': EventType(3, '승객 하차', 10),

    'end': EventType(4, '차고 귀환', 0)

}


class Event:

    def __init__(self, taxi_id, event_time, event_type):

        self.taxi_id = taxi_id

        self.time = event_time

        self.type = event_type


    def __lt__(self, other):

        if not self.time == other.time:

            return self.time < other.time

        return self.taxi_id < other.taxi_id


    def __repr__(self):

        return '시각 {:03d}:'.format(self.time) +

            '    ' * (self.taxi_id + 1) +

            '택시 {} - {}'.format(self.taxi_id+1, self.type.message)


class EventLoop:

    def __init__(self, taxies):

        self.taxies = taxies

        self.events = queue.PriorityQueue()


    def run_until_complete(self):

        loop_time = 0

        for _, taxi in self.taxies.items():

            self.events.put(next(taxi))


        while True:

            if self.events.empty():

                break

            event = self.events.get()

            print(event)

            taxi = self.taxies[event.taxi_id]

            loop_time = event.time

            next_time = loop_time + int(random.random() * event.type.interval) + 1

            end_time = loop_time + 1

            try:

                next_event = taxi.send((next_time, end_time))

            except StopIteration:

                del(self.taxies[event.taxi_id])

            else:

                self.events.put(next_event)


def taxi(taxi_id, trips_num, start_time):

    event_time, _ = yield Event(taxi_id, start_time, event_types['start'])

    for _ in range(trips_num):

        event_time, _ = yield Event(taxi_id, event_time, event_types['pick'])

        event_time, end_time = yield Event(taxi_id, event_time, event_types['drop'])

    yield Event(taxi_id, end_time, event_types['end'])


def main(taxi_numbers=3, trips_max=5, start_interval=5):

    taxies = {i: taxi(i, random.randint(1, trips_max), i*start_interval)

                for i in range(taxi_numbers)}

    sim = EventLoop(taxies)

    sim.run_until_complete()


if __name__ == '__main__':

    main()


-- 출력 예시(실행시마다 다름)


시각 000:    택시 1 - 차고 출발

시각 005:        택시 2 - 차고 출발

시각 006:        택시 2 - 승객 탑승

시각 009:        택시 2 - 승객 하차

시각 010:    택시 1 - 승객 탑승

시각 010:            택시 3 - 차고 출발

시각 016:    택시 1 - 승객 하차

시각 017:            택시 3 - 승객 탑승

시각 019:        택시 2 - 승객 탑승

시각 024:    택시 1 - 승객 탑승

시각 027:            택시 3 - 승객 하차

시각 028:            택시 3 - 승객 탑승

시각 031:            택시 3 - 승객 하차

시각 033:            택시 3 - 승객 탑승

시각 035:    택시 1 - 승객 하차

시각 037:            택시 3 - 승객 하차

시각 038:        택시 2 - 승객 하차

시각 038:            택시 3 - 차고 귀환

시각 040:    택시 1 - 승객 탑승

시각 041:        택시 2 - 승객 탑승

시각 044:    택시 1 - 승객 하차

시각 045:    택시 1 - 차고 귀환

시각 053:        택시 2 - 승객 하차

시각 058:        택시 2 - 승객 탑승

시각 059:        택시 2 - 승객 하차

시각 060:        택시 2 - 차고 귀환