다양한 환경의 이벤트 루프 구현 사례

이벤트 루프는 여러 시스템과 언어에서 중요한 패턴으로 사용됩니다. 각 환경마다 구현 방식이 다르지만 개념적으로는 유사합니다. 아래에 몇 가지 유명한 이벤트 루프 구현 사례를 소개합니다.

1. Node.js의 libuv 이벤트 루프

Node.js의 이벤트 루프는 libuv 라이브러리에 의해 구현되며, C로 작성되어 있습니다. 다음은 libuv의 핵심 이벤트 루프 코드입니다:

int uv_run(uv_loop_t* loop, uv_run_mode mode) {
  int timeout;
  int r;
  int ran_pending;

  r = uv__loop_alive(loop);
  if (!r)
    uv__update_time(loop);

  while (r != 0 && loop->stop_flag == 0) {
    uv__update_time(loop);
    uv__run_timers(loop);
    ran_pending = uv__run_pending(loop);
    uv__run_idle(loop);
    uv__run_prepare(loop);

    timeout = 0;
    if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
      timeout = uv_backend_timeout(loop);

    uv__io_poll(loop, timeout);
    uv__run_check(loop);
    uv__run_closing_handles(loop);

    if (mode == UV_RUN_ONCE) {
      uv__update_time(loop);
      uv__run_timers(loop);
    }

    r = uv__loop_alive(loop);
    if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
      break;
  }

  return r;
}

이 코드는 Node.js의 비동기 I/O 모델의 핵심이며, 여러 단계(phases)로 이벤트 루프를 실행합니다.

2. Python의 asyncio 이벤트 루프

Python 3.4부터 도입된 asyncio 라이브러리의 이벤트 루프 구현 일부입니다:

def run_forever(self):
    """Run the event loop until stop() is called."""
    self._check_closed()
    self._thread_id = threading.get_ident()

    old_agen_hooks = sys.get_asyncgen_hooks()
    sys.set_asyncgen_hooks(firstiter=self._asyncgen_firstiter_hook,
                           finalizer=self._asyncgen_finalizer_hook)
    try:
        events._set_running_loop(self)
        while True:
            self._run_once()
            if self._stopping:
                break
    finally:
        self._stopping = False
        events._set_running_loop(None)
        sys.set_asyncgen_hooks(*old_agen_hooks)

def _run_once(self):
    """Run one full iteration of the event loop.

    This calls all currently ready callbacks, polls for I/O,
    schedules the resulting callbacks, and finally schedules
    'call_later' callbacks.
    """
    sched_count = len(self._scheduled)
    if (sched_count > _MIN_SCHEDULED_TIMER_HANDLES and
        self._timer_cancelled_count / sched_count >
            _MIN_CANCELLED_TIMER_HANDLES_FRACTION):
        # Remove delayed calls that were cancelled
        new_scheduled = []
        for handle in self._scheduled:
            if handle._cancelled:
                handle._scheduled = False
            else:
                new_scheduled.append(handle)

        heapq.heapify(new_scheduled)
        self._scheduled = new_scheduled
        self._timer_cancelled_count = 0
    else:
        # Remove delayed calls that were cancelled from head of queue
        while self._scheduled and self._scheduled[0]._cancelled:
            self._timer_cancelled_count -= 1
            handle = heapq.heappop(self._scheduled)
            handle._scheduled = False

    timeout = None
    if self._ready or self._stopping:
        timeout = 0
    elif self._scheduled:
        # Compute the desired timeout
        when = self._scheduled[0]._when
        timeout = max(0, when - self.time())

    # ... (이벤트 폴링 및 콜백 처리 코드)
    
    # 타이머 이벤트 처리
    end_time = self.time() + self._clock_resolution
    while self._scheduled:
        handle = self._scheduled[0]
        if handle._when > end_time:
            break
        handle = heapq.heappop(self._scheduled)
        handle._scheduled = False
        self._ready.append(handle)

    # 준비된 콜백 실행
    ntodo = len(self._ready)
    for i in range(ntodo):
        handle = self._ready.popleft()
        if handle._cancelled:
            continue
        handle._run()

이 코드는 Python에서 비동기 프로그래밍을 가능하게 하는 핵심 구성 요소입니다.

3. FreeRTOS의 태스크 스케줄러

FreeRTOS는 임베디드 시스템을 위한 실시간 운영체제로, 이벤트 루프와 유사한 태스크 스케줄링 메커니즘을 사용합니다:

void vTaskStartScheduler( void )
{
    /* 스케줄러 초기화 및 첫 번째 태스크 시작 */
    if( xPortStartScheduler() != pdFALSE )
    {
        /* 스케줄러가 종료되면 여기로 돌아오지 않음 */
    }
}

BaseType_t xTaskIncrementTick( void )
{
    TCB_t * pxTCB;
    TickType_t xItemValue;
    BaseType_t xSwitchRequired = pdFALSE;

    /* 시스템 틱 카운터 증가 */
    const TickType_t xConstTickCount = xTickCount + 1;
    xTickCount = xConstTickCount;

    /* 틱 카운트가 0이면 delayed 태스크 리스트 교체 */
    if( xConstTickCount == ( TickType_t ) 0U )
    {
        taskSWITCH_DELAYED_LISTS();
    }
    else
    {
        /* 지연 만료된 태스크 확인 */
        if( xConstTickCount >= xNextTaskUnblockTime )
        {
            for( ;; )
            {
                if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
                {
                    /* 지연 리스트가 비었으면 최대값으로 설정 */
                    xNextTaskUnblockTime = portMAX_DELAY;
                    break;
                }
                else
                {
                    /* 리스트에서 첫 번째 항목 가져오기 */
                    pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
                    xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );

                    /* 틱 카운트가 항목 값보다 작으면 만료된 태스크 없음 */
                    if( xConstTickCount < xItemValue )
                    {
                        xNextTaskUnblockTime = xItemValue;
                        break;
                    }

                    /* 태스크를 지연 리스트에서 준비 리스트로 이동 */
                    ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
                    
                    if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
                    {
                        ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
                    }
                    prvAddTaskToReadyList( pxTCB );

                    /* 우선순위가 현재 실행 중인 태스크보다 높은지 확인 */
                    if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
                    {
                        xSwitchRequired = pdTRUE;
                    }
                }
            }
        }
    }

    /* 태스크 실행 시간 트래킹 업데이트 */
    #if ( configUSE_TICK_HOOK == 1 )
    {
        vApplicationTickHook();
    }
    #endif

    return xSwitchRequired;
}

FreeRTOS의 스케줄러는 우선순위 기반으로 태스크를 관리하며, 실시간 시스템에 적합한 예측 가능한 동작을 제공합니다.

4. Java의 NIO Selector (이벤트 루프)

Java NIO는 논블로킹 I/O를 지원하며, Selector 클래스를 통해 이벤트 루프를 구현합니다:

public abstract class Selector implements Closeable {
    // 셀렉터 생성
    public static Selector open() throws IOException {
        return SelectorProvider.provider().openSelector();
    }

    // 키 선택 메서드
    public abstract int select() throws IOException;
    public abstract int select(long timeout) throws IOException;
    public abstract int selectNow() throws IOException;
    public abstract Selector wakeup();
    
    // 선택된 키 세트 반환
    public abstract Set<SelectionKey> selectedKeys();
    
    // 등록된 키 세트 반환
    public abstract Set<SelectionKey> keys();
    
    // 이벤트 루프 사용 예시
    public static void eventLoop() throws IOException {
        Selector selector = Selector.open();
        
        // 채널 등록 코드...
        
        while (true) {
            // 이벤트 발생 대기
            int readyChannels = selector.select();
            
            if (readyChannels == 0) {
                continue;
            }
            
            // 준비된 키 처리
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
            
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                
                if (key.isAcceptable()) {
                    // 연결 수락 처리
                } else if (key.isReadable()) {
                    // 읽기 처리
                } else if (key.isWritable()) {
                    // 쓰기 처리
                }
                
                keyIterator.remove();
            }
        }
    }
}

Java NIO Selector는 다중 채널에서 발생하는 I/O 이벤트를 효율적으로 감지하고 처리하는 메커니즘을 제공합니다.

5. JavaScript 브라우저의 이벤트 루프 (V8 엔진)

크롬 브라우저의 V8 엔진 내부 이벤트 루프 구현의 일부입니다:

void Isolate::RunMicrotasks() {
  TRACE_EVENT0("v8", "V8.RunMicrotasks");
  TRACE_EVENT_CALL_STATS_SCOPED(this, "v8", "V8.RunMicrotasks");
  
  if (thread_local_top()->microtask_pending_) {
    DCHECK(this->micro_task_queue_);
    this->micro_task_queue_->RunMicrotasks(this);
  }
}

void MicrotaskQueue::RunMicrotasks(Isolate* isolate) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.microtasks"),
               "V8.RunMicrotasks");
  
  MicrotasksScope::PerformCheckpoint(isolate);
}

void MicrotasksScope::PerformCheckpoint(Isolate* isolate) {
  auto* microtask_queue = isolate->default_microtask_queue();
  microtask_queue->PerformCheckpoint(isolate);
}

void MicrotaskQueue::PerformCheckpoint(Isolate* isolate) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.microtasks"),
               "MicrotaskQueue::PerformCheckpoint");
  
  if (!microtasks_policy_) return;
  
  // Increase the microtask depth used by MicrotasksScopes.
  int current_microtask_depth = depth_ + 1;
  depth_ = current_microtask_depth;
  
  // 마이크로태스크 처리
  while (!capacity_ && pending_microtask_count_) {
    Microtask task = data_[start_];
    
    // 원형 큐 처리
    start_ = (start_ + 1) % capacity_;
    pending_microtask_count_--;
    
    TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.microtasks"),
                 "V8.RunMicrotask");
                 
    // 마이크로태스크 실행
    task.Run(isolate);
    
    if (depth_ != current_microtask_depth) {
      // 실행 중에 새 마이크로태스크가 추가되었으므로 루프 계속
      current_microtask_depth = depth_;
    }
  }
  
  // 깊이 복원
  depth_ = current_microtask_depth - 1;
  
  // 매크로태스크 처리 (브라우저 내부 구현)
  // ...
}

이 코드는 JavaScript 비동기 모델의 핵심인 마이크로태스크 큐를 처리하는 부분입니다.

각 구현은 특정 환경의 요구사항에 맞게 최적화되어 있지만, 공통적으로 이벤트를 수집하고 처리하는 루프 구조를 갖추고 있습니다. 이러한 이벤트 루프는 현대 소프트웨어 시스템의 반응성과 효율성을 높이는 핵심 메커니즘입니다.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다