Chat Conversation

참여자: User, QueryPie

Message 1 from User

fiber가 뭐야? 스레드나 프로세스와 비교하여 간략히 설명해줘

Message 2 from QueryPie

Fiber는 사용자 수준(user-level)에서 관리되는 경량 실행 단위로, 협력적 멀티태스킹(cooperative multitasking)을 구현하는 방식입니다. 스레드보다 더 가벼우며, 개발자가 직접 컨텍스트 스위칭을 제어할 수 있습니다.

주요 차이점 비교

특성 프로세스 스레드 Fiber
무게 가장 무거움 중간 가장 가벼움
메모리 공간 독립적 프로세스 내 공유 스레드 내 공유
스케줄링 OS 커널 OS 커널 사용자/라이브러리
컨텍스트 스위칭 매우 느림 느림 매우 빠름
생성 비용 높음 중간 낮음
전환 방식 선점형(preemptive) 선점형 협력형(cooperative)

핵심 특징

Fiber의 장점

  • 컨텍스트 스위칭이 매우 빠름 (커널 개입 없음)
  • 메모리 오버헤드가 작음 (수천~수만 개 생성 가능)
  • 개발자가 실행 흐름을 명시적으로 제어
  • 동기적 코드 스타일로 비동기 작업 처리 가능

Fiber의 단점

  • 명시적으로 yield를 호출해야 함 (자동 전환 안 됨)
  • 하나의 fiber가 CPU를 독점하면 다른 fiber가 실행 안 됨
  • 멀티코어 활용이 제한적 (스레드와 함께 사용해야 함)

실제 사용 예시

Fiber는 Go의 goroutine, Kotlin의 coroutine, Ruby의 fiber 등으로 구현되며, 주로 다음과 같은 상황에서 사용됩니다:

  • 대량의 동시 I/O 작업 처리 (웹 서버, 네트워크 애플리케이션)
  • 게임 엔진의 경량 태스크 시스템
  • 비동기 프로그래밍을 동기 스타일로 작성

간단히 말해, 프로세스 > 스레드 > Fiber 순으로 가벼워지며, 제어권도 OS → 사용자로 이동합니다. Fiber는 성능과 동시성이 중요한 현대 애플리케이션에서 효율적인 선택지입니다.

궁금한 특정 언어나 프레임워크의 fiber 구현이 있나요?

Message 3 from User

React Fiber 구현체를 예시로 들어 상세히 설명해줘

Message 4 from QueryPie

React Fiber는 React 16에서 도입된 새로운 재조정(reconciliation) 엔진으로, React의 렌더링 작업을 작은 단위로 쪼개어 우선순위를 매기고 중단/재개할 수 있게 만든 아키텍처입니다.

React Fiber의 핵심 개념

React Fiber는 전통적인 fiber의 개념을 차용하여, 각 React 컴포넌트를 fiber 객체라는 작업 단위로 표현합니다. 이를 통해 렌더링 작업을 여러 프레임에 걸쳐 분산시켜 메인 스레드를 블로킹하지 않습니다.

Fiber 자료구조

각 fiber는 다음과 같은 정보를 담고 있는 JavaScript 객체입니다:

{
  // 컴포넌트 타입 정보
  type: 'div' | Function | Class,
  key: 'unique-key',
  
  // 트리 구조를 위한 포인터
  child: Fiber,      // 첫 번째 자식
  sibling: Fiber,    // 다음 형제
  return: Fiber,     // 부모 (return은 작업 완료 후 돌아갈 곳)
  
  // 상태 관리
  stateNode: DOMNode | Instance,  // 실제 DOM 노드나 컴포넌트 인스턴스
  memoizedState: any,              // 이전 상태
  memoizedProps: any,              // 이전 props
  pendingProps: any,               // 새로운 props
  
  // 작업 추적
  effectTag: 'PLACEMENT' | 'UPDATE' | 'DELETION',
  alternate: Fiber,    // 작업 중인 fiber와 현재 fiber (더블 버퍼링)
  
  // 스케줄링
  lanes: Lanes,        // 우선순위 정보
}

동작 원리

1. 더블 버퍼링 (Double Buffering)

React는 두 개의 fiber 트리를 유지합니다:

  • current tree: 현재 화면에 렌더링된 상태
  • workInProgress tree: 작업 중인 새로운 상태
// 현재 화면
current fiber tree (화면에 표시됨)
      ↕ alternate
workInProgress tree (백그라운드에서 작업)

// 작업 완료 후 포인터만 교체
workInProgress → current (commit phase에서 swap)

2. 작업 단위 분할 (Unit of Work)

렌더링 작업이 다음과 같이 분할됩니다:

function performUnitOfWork(fiber) {
  // 1. beginWork: 현재 fiber 처리
  //    - props 비교
  //    - 자식 생성
  //    - effectTag 설정
  let next = beginWork(fiber);
  
  if (next) {
    // 자식이 있으면 자식으로 이동
    return next;
  }
  
  // 2. completeWork: 작업 완료
  //    - DOM 노드 생성/업데이트 준비
  completeUnitOfWork(fiber);
}

function workLoop(deadline) {
  while (nextUnitOfWork && deadline.timeRemaining() > 1) {
    // 시간이 남아있으면 작업 계속
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
  }
  
  if (nextUnitOfWork) {
    // 시간 부족하면 다음 프레임에 예약
    requestIdleCallback(workLoop);
  } else {
    // 모든 작업 완료 → commit phase
    commitRoot();
  }
}

3. 두 단계 렌더링

// Render Phase (중단 가능, 비동기)
// - 새로운 fiber 트리 구축
// - 변경사항 계산
// - effectTag 설정
function renderPhase() {
  // 이 단계는 언제든 중단/재개/폐기 가능
  while (workInProgress) {
    workInProgress = performUnitOfWork(workInProgress);
    
    if (shouldYield()) {
      // 브라우저에게 제어권 반환
      return; // 나중에 이어서 작업
    }
  }
}

// Commit Phase (중단 불가, 동기)
// - DOM에 실제 반영
// - lifecycle 메서드 실행
function commitPhase(finishedWork) {
  // 이 단계는 한 번에 실행되어야 함
  commitBeforeMutationEffects(finishedWork);
  commitMutationEffects(finishedWork);  // DOM 변경
  commitLayoutEffects(finishedWork);    // useEffect 등 실행
}

우선순위 시스템 (Lanes)

React Fiber는 작업에 우선순위를 부여합니다:

// 우선순위 레벨 (간소화)
const SyncLane = 0b0001;           // 가장 높음 (동기 업데이트)
const InputContinuousLane = 0b0010; // 사용자 입력
const DefaultLane = 0b0100;         // 일반 업데이트
const TransitionLane = 0b1000;      // 낮음 (startTransition)

// 예시: 사용자 입력은 높은 우선순위
function handleInput(e) {
  // 높은 우선순위로 상태 업데이트
  setState(e.target.value);
}

// 예시: 데이터 페칭은 낮은 우선순위
function loadData() {
  startTransition(() => {
    // 낮은 우선순위로 상태 업데이트
    setData(fetchedData);
  });
}

실제 동작 예시

function App() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>+</button>
      <ExpensiveList />
    </div>
  );
}

버튼 클릭 시 내부 동작:

// 1. 상태 업데이트 스케줄링
setCount(1) 
  → scheduleUpdateOnFiber(fiber, lane)
  → ensureRootIsScheduled(root)

// 2. Render Phase 시작
workLoop() {
  // App fiber 처리
  performUnitOfWork(AppFiber)
    → beginWork: props 비교, 새로운 자식 생성
    
  // div fiber 처리
  performUnitOfWork(divFiber)
    → child: h1Fiber
    
  // h1 fiber 처리 (count 변경 감지)
  performUnitOfWork(h1Fiber)
    → effectTag = UPDATE
    → memoizedProps = {children: 1}
    
  // 16ms 경과 체크
  if (deadline.timeRemaining() < 1) {
    // 중단하고 다음 프레임에 계속
    return;
  }
  
  // button, ExpensiveList 계속 처리...
}

// 3. Commit Phase (모든 작업 완료 후)
commitRoot() {
  // DOM 실제 업데이트
  h1Element.textContent = '1';
  
  // workInProgress ↔ current 교체
  root.current = finishedWork;
}

React Fiber의 실질적 이점

이전 Stack Reconciler의 문제

// React 15 이전: 재귀적 처리
function reconcile(element) {
  // 중단할 수 없음!
  element.children.forEach(child => {
    reconcile(child); // 깊은 트리는 프레임 드롭 발생
  });
}

Fiber의 해결책

// React 16+: 반복적 처리
function reconcile() {
  while (workInProgress && !shouldYield()) {
    workInProgress = performUnitOfWork(workInProgress);
  }
  // 언제든 중단 가능, 나중에 재개
}

구체적인 사용 예시:

// 긴급한 입력은 즉시 처리
<input onChange={handleChange} /> // 높은 우선순위

// 무거운 렌더링은 뒤로 미룸
<Suspense fallback={<Spinner />}>
  <ExpensiveComponent />
</Suspense>

// startTransition으로 명시적 우선순위 지정
startTransition(() => {
  setSearchResults(results); // 낮은 우선순위
});

핵심 요약

React Fiber는 전통적인 fiber 개념을 웹 환경에 맞게 구현한 것으로:

  • 각 컴포넌트를 fiber 객체로 표현하여 작업 단위화
  • 협력적 스케줄링으로 브라우저에게 제어권 양보
  • 우선순위 기반 렌더링으로 중요한 업데이트 우선 처리
  • 더블 버퍼링으로 일관성 있는 UI 업데이트

이를 통해 React는 복잡한 UI도 60fps를 유지하며 부드럽게 렌더링할 수 있습니다.

궁금한 특정 부분이나 더 깊이 알고 싶은 메커니즘이 있나요?