운영체제 용어 사전
프로세스커널 스레드 · M:N · 그린 스레드

유저 스레드

누가 스레드를 스케줄하는지에 따른 구분. 1:1·N:1·M:N 모델이 있다.

스레드를 누가 스케줄하느냐에 따른 구분.

  • 커널 스레드(1:1) — 커널이 안다. 커널이 스케줄한다

    • 진짜 병렬 실행 가능. 하나가 블로킹돼도 다른 것은 돈다
    • 생성·전환이 비싸다 (시스템 콜)
  • 유저 스레드(N:1) — 커널은 모른다. 런타임 라이브러리가 스케줄한다

    • 생성·전환이 매우 싸다
    • 하나가 블로킹하면 전부 멈춘다. 멀티코어를 못 쓴다
  • M:N — 유저 스레드 M 개를 커널 스레드 N 개에 다중화

    • 둘의 장점을 노리지만 구현이 복잡하다

왜 N:1이 블로킹에 약한가

커널 입장에서는 프로세스에 스레드가 하나뿐이다 그 하나가 read() 로 블로킹되면 커널은 프로세스 전체를 재운다

  • 런타임이 다른 유저 스레드를 돌리고 싶어도 기회가 없다

현대의 M:N 사례

  • Go 고루틴 — 런타임이 M:N 으로 다중화. 블로킹 시스템 콜 시

    • 런타임이 다른 커널 스레드를 붙여 준다
  • 자바 가상 스레드 — Java 21. 캐리어 스레드 풀 위에서 다중화

    • 블로킹 I/O 시 unmount 되어 캐리어를 놓아 준다
  • 둘 다 '블로킹 코드를 그대로 쓰면서 대규모 동시성' 을 노린다

왜 예전에 M:N이 실패했나

1990년대 여러 유닉스가 M:N 을 시도했다가 1:1 로 회귀했다

  • 스케줄러가 둘(커널·런타임) 이라 상호작용이 복잡했다
  • 시그널·디버깅·우선순위 처리가 어려웠다

지금 다시 되는 이유는 언어 런타임이 I/O 까지 통합 관리하기 때문이다

스택 크기가 만드는 차이

  • OS 스레드 — 스택이 고정 크기로 예약된다 (리눅스 기본 8MB 가상 예약)

    • 수만 개를 만들면 가상 주소 공간과 커널 메모리가 부담
    • 실제 커밋은 쓰는 만큼이지만 커널 자료구조는 실제로 든다
  • 고루틴 — 2KB 로 시작해 필요하면 늘린다(스택 복사·확장) 가상 스레드 스택을 힙에 청크로 둔다. 대기 중이면 힙 객체일 뿐이다

  • '수십만 개 동시성' 이 가능한 근본 이유는 스케줄링보다 스택에 있다

마운트·언마운트

가상 스레드가 블로킹 I/O 를 만나면

  • 캐리어 스레드에서 분리(unmount)되고 스택이 힙에 보관된다
  • I/O 가 끝나면 아무 캐리어에나 다시 붙는다(mount)

분리가 안 되는 경우 = pinning

  • synchronized 블록 안 (JDK 21~23) — JDK 24 JEP 491 에서 해소

  • 네이티브 프레임(JNI) 안

  • pinning 이 나면 캐리어가 묶여 처리량이 급락한다

  • jdk.tracePinnedThreads 로 추적한다

언제 이득이 없나

  • CPU 바운드 작업 — 코어 수만큼만 병렬이므로 이득이 없다
    • 오히려 전환·스케줄링 오버헤드만 는다
  • 스레드 로컬 남용 — 경량 스레드를 수십만 개 만들면 ThreadLocal 이 그만큼 복제된다
    • ScopedValue(JEP 506) 로 대체하는 흐름

면접 함정

  • "유저 스레드가 항상 빠르다" → 전환은 싸지만 멀티코어와 블로킹에 약하다.
  • "가상 스레드는 OS 스레드보다 많이 만들 수 있으니 무조건 좋다" → CPU 바운드 작업에는 이득이 없다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 스레드 — 공유·전환 비용·유저/커널·가상 스레드