[카테고리:] 미분류

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

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

    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 비동기 모델의 핵심인 마이크로태스크 큐를 처리하는 부분입니다.

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