Chat Conversation
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를 유지하며 부드럽게 렌더링할 수 있습니다.
궁금한 특정 부분이나 더 깊이 알고 싶은 메커니즘이 있나요?