# Zig의 비동기 프로그램에 대한 새로운 계획

> Clean Markdown view of GeekNews topic #24809. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=24809](https://news.hada.io/topic?id=24809)
- GeekNews Markdown: [https://news.hada.io/topic/24809.md](https://news.hada.io/topic/24809.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2025-12-04T01:33:43+09:00
- Updated: 2025-12-04T01:33:43+09:00
- Original source: [lwn.net](https://lwn.net/SubscriberLink/1046084/4c048ee008e1c70e/)
- Points: 4
- Comments: 1

## Topic Body

- **Zig 언어**가 기존 비동기 I/O 설계의 복잡성을 줄이기 위해 **새로운 `Io` 인터페이스 기반 모델**을 도입  
- 이 모델은 **동기·비동기 코드의 구분 없이 동일한 함수 구조**를 유지하며, `Io.Threaded`와 `Io.Evented` 두 구현을 제공  
- `Io.Threaded`는 기본적으로 **동기 실행**을, `Io.Evented`는 **이벤트 루프 기반 비동기 실행**을 수행  
- 개발자는 `async()`와 `concurrent()` 함수를 통해 **병렬 실행 제어**가 가능하며, 코드 수정 없이 성능 최적화 가능  
- 이 접근은 **함수 색칠(function coloring)** 문제를 해결하고, Zig의 **단순성과 제어성**을 유지한 채 비동기 성능을 확보하는 방향  

---

### Zig의 비동기 설계 변화
- Zig는 기존 비동기 설계가 언어의 **미니멀리즘 철학**과 잘 맞지 않아 새로운 접근을 모색  
  - 기존 설계는 다른 기능과의 통합성이 낮았음  
  - 새 모델은 **동기·비동기 I/O를 동일한 코드 구조로 처리** 가능  
- 새로운 설계는 **`Io` 제너릭 인터페이스**를 중심으로 동작  
  - 모든 I/O 함수는 `Io` 인스턴스를 매개변수로 받아 실행  
  - `Allocator` 인터페이스와 유사한 구조로, 메모리 할당과 같은 방식으로 I/O 제어 가능  

### `Io` 인터페이스의 구조
- 표준 라이브러리에 **두 가지 기본 구현체** 포함  
  - **`Io.Threaded`** : 기본적으로 동기 실행, 필요 시 스레드 병렬 처리  
  - **`Io.Evented`** : 이벤트 루프 기반 비동기 실행 (`io_uring`, `kqueue` 등 사용)  
- 사용자는 직접 **새로운 `Io` 구현체를 작성**할 수 있어, 실행 방식에 대한 세밀한 제어 가능  

### 코드 예시와 동작 방식
- 예시 함수 `saveFile()`은 파일 생성, 쓰기, 닫기를 수행  
  - `Io.Threaded` 사용 시 일반 시스템 호출로 동작  
  - `Io.Evented` 사용 시 비동기 백엔드로 실행  
  - 두 경우 모두 `writeAll()` 호출 시점에 작업 완료 보장  
- 동일한 코드가 **동기·비동기 환경 모두에서 동일하게 작동**  
  - 라이브러리 작성자는 실행 방식에 신경 쓸 필요 없음  

### 병렬 실행과 `async()` / `concurrent()`
- `async()` 함수는 **비동기 실행을 요청**하지만, `Io.Threaded`에서는 즉시 실행될 수도 있음  
  - `Io.Evented`에서는 실제 비동기 실행으로 두 파일을 동시에 저장 가능  
- `concurrent()` 함수는 **실제 병렬 실행이 필요한 경우** 사용  
  - `Io.Threaded`는 스레드 풀을 활용  
  - `Io.Evented`는 `async()`와 동일하게 처리  
- 잘못된 함수 선택(`async` 대신 `concurrent`)은 **버그로 간주**, 언어 차원에서 방지 불가  

### 코드 스타일과 언어 통합
- 비동기 전용 문법 없이 **일반 Zig 코드 스타일 유지**  
  - `try`, `defer` 등 기존 제어 흐름 문법 그대로 사용  
  - Andrew Kelley는 “표준 Zig 코드처럼 읽힌다”고 언급  
- 예시로 **비동기 DNS 조회 구현** 제시  
  - `getaddrinfo()`와 달리 첫 번째 성공 응답만 반환하고 나머지 요청은 취소  

### 향후 계획과 개발 현황
- `Io.Evented`는 아직 **실험적 단계**, 일부 OS 미지원  
- **WebAssembly 호환 `Io` 구현**이 계획 중이며, 관련 기능 개발 필요  
- `Io` 관련 **24개의 후속 작업 항목**이 존재하며 대부분 미완료 상태  
- Zig는 아직 1.0 버전 전으로, **비동기 I/O와 네이티브 코드 생성**이 주요 남은 과제  
- 이번 설계로 **I/O 인터페이스 변경으로 인한 코드 재작성 빈도 감소** 기대  

### 커뮤니티 논의 요약
- 여러 댓글에서 Zig의 접근이 **Rust의 async/await 모델보다 단순하고 유연**하다는 평가  
  - Rust는 여러 executor 혼용 시 복잡성이 높음  
  - Zig는 `Io` 인터페이스로 **다중 executor 공존 가능성 확보**  
- 일부는 코드가 다소 **장황해질 수 있음**을 지적  
  - 그러나 명시적 API 설계로 **보안·성능·테스트 제어성 향상**  
- 비동기 실행과 스레드 실행의 차이, **stackful vs stackless coroutine** 구현 방식 등 기술적 논의도 이어짐  
- Zig의 `Io`는 **언어 차원의 특별한 처리 없이** 표준 라이브러리 확장 형태로 구현됨  
  - 향후 **stackless coroutine 기능**이 추가될 예정  

### 결론
- Zig의 새 비동기 모델은 **언어 단순성 유지와 고성능 I/O 양립**을 목표로 함  
- **함수 색칠 문제 해결**, **동기·비동기 코드 통합**, **명시적 제어 구조**를 통해  
  **Zig 1.0 안정화의 핵심 단계**로 평가됨

## Comments



### Comment 47160

- Author: neo
- Created: 2025-12-04T01:33:43+09:00
- Points: 1

###### [Hacker News 의견](https://news.ycombinator.com/item?id=46121539) 
- 전체적으로 이 글은 정확하고 잘 조사된 내용임  
  다만 몇 가지 **작은 수정 사항**이 있음.  
  `Io.Threaded` 인스턴스에서 `async()`는 실제로 비동기로 동작하지 않고 즉시 실행됨. 하지만 `std.Io.Threaded`는 기본적으로 **스레드 풀**을 사용해 비동기 작업을 분배함.  
  단, `init_single_threaded`로 초기화하면 기사에서 설명한 동작처럼 동작함.  
  또 하나, 예전에는 `asyncConcurrent()`라는 함수가 있었지만 지금은 단순히 `concurrent()`로 이름이 바뀌었음
  - Daroc임. 이 피드백을 반영해 기사에 두 가지 **수정 사항**을 적용했음.  
    앞으로 피드백을 주려면 [lwn@lwn.net](mailto:lwn@lwn.net)으로 메일을 보내면 됨.  
    수정 제안과 Zig 관련 작업에 감사함
  - Andrew에게 질문이 있음.  
    만약 `async()`를 써야 할 곳에 `asyncConcurrent()`를 잘못 쓰면 어떤 **버그**가 생기는지 궁금함.  
    IO 모델에 따라 **UB(정의되지 않은 동작)** 이 될 수도 있는지, 아니면 단순한 논리 오류인지 알고 싶음
  - `concurrent()`의 좋은 점은 코드의 **가독성과 표현력**을 높여, “이 코드는 반드시 병렬로 실행돼야 함”을 명확히 보여준다는 점임

- 이 설계는 꽤 합리적이라고 생각함.  
  다만 Zig의 설명은 혼란스러움.  
  함수 색칠 문제(function coloring)를 해결했다고 강조하지만, 사실상 **효과 타입(effect type)** 으로 IO를 밀어 넣은 것에 불과함.  
  이는 호출자가 토큰을 유지해야 하는 형태로, 여전히 일종의 색칠임.  
  Go의 비동기 처리 방식과 유사하다고 봄
  - 만약 인자만 다르게 호출해도 ‘색칠된 함수’라면, 모든 함수가 색칠된 셈이 되어 의미가 사라짐 ;)  
    Zig의 예전 async-await 모델도 이미 coloring 문제를 해결했었음.  
    컴파일러가 호출 문맥에 따라 **동기/비동기 버전**을 자동으로 생성했기 때문임
  - 실제로 function coloring의 핵심 문제는 **동기/비동기 코드 경로의 중복**임.  
    Zig는 이를 **의존성 주입**으로 해결해 실용적으로 충분함.  
    async 호출의 복잡성은 피할 수 없지만, 정밀한 제어를 위해선 어쩔 수 없는 부분임
  - Zig의 io는 전염성 있는 effect type이 아님.  
    전역 io 변수를 선언해 어디서든 쓸 수 있음(물론 라이브러리 작성 시엔 권장되지 않음).  
    function coloring 문제의 다섯 가지 조건을 정리한 [What color is your function?](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/) 글을 보면, Zig의 접근이 일부 조건(특히 4, 5)을 만족하지 못할 가능성이 높음
  - 사실상 Zig는 모든 것을 **async로 색칠**하고, 워커 스레드를 쓸지 말지만 선택하게 한 것 같음.  
    하지만 이런 접근은 **데드락** 같은 문제를 유발할 수 있음.  
    일부 코드는 스레드 안전하지 않기 때문에 coloring이 오히려 도움이 됨
  - Haskell 개발자로서 보면, Zig는 언어 지원 없이 **IO 모나드**를 구현한 셈처럼 보임

- 이 설계는 Scala의 async와 매우 비슷해 보임.  
  Scala에서는 실행 컨텍스트가 **암시적 파라미터**로 전달되는데, Zig는 명시적으로 받음.  
  실제로는 스레드와 큐를 직접 쓰는 것보다 나은 점이 많지 않았고, 실행 컨텍스트 관리가 **복잡하고 예측 불가한 동작**을 유발했음.  
  Zig 팀이 Scala 경험이 적어서 이 접근이 새롭다고 생각한 듯함
  - OS 스레드를 직접 쓰면 **Little의 법칙**에 따라 확장성 한계에 부딪힘.  
    JVM은 **가상 스레드**로 이를 해결하지만, 저수준 언어는 같은 효율을 내기 어려움.  
    따라서 Zig 같은 언어는 다른 방식의 확장성 솔루션이 필요함
  - 참고로 Scala의 [ExecutionContext API](https://www.scala-lang.org/api/current/scala/concurrent/ExecutionContext.html)를 보면 관련 개념을 더 잘 이해할 수 있음

- 예전 Zig의 async/await 시스템에서는 함수의 **suspend/resume**이 가능했음.  
  이 기능으로 OS 개발 시 디바이스 인터럽트 기반의 **프레임 일시 중단/재개**를 구현해보고 싶었음.  
  새 io 시스템에서는 이걸 직접 구현해야 할 것 같아 아쉬움
  - `@asyncSuspend`와 `@asyncResume`이라는 **저수준 빌트인**이 존재함.  
    새로운 Io는 동기, 스레드, 이벤트 기반의 공통 추상화이므로 suspend 메커니즘은 포함되지 않음
  - 최종적으로는 suspend/resume이 **사용자 공간 표준 라이브러리 함수**로 구현될 가능성이 있음.  
    현재 [Io.Evented 프로토타입](https://github.com/ziglang/zig/issues/23446)을 보면, **stackless coroutine** 기반으로 3rd-party 라이브러리에서 다룰 수도 있음
  - 스레드 풀을 하나만 두고 suspend/resume을 구현할 수 있는지도 궁금함
  - 협력형 코루틴을 **선점형 async**로 구현하는 게 어떤 의미가 있는지도 의문임

- 예시 코드에서 `writeAll()`이 반환될 때 작업이 완료된다고 했는데,  
  IO 구현이 다양할 수 있으므로 실제로는 **defer가 시작될 때** 완료가 보장돼야 함.  
  그렇지 않으면 `createFile`과 `writeAll` 간의 **의존 관계 추적**이 필요함.  
  그렇다면 결국 **blocking 호출**과 다를 게 없어 보임.  
  또한 이 인터페이스 이름이 IO인 이유도 모호함.  
  실제로는 “다른 컨텍스트에서 실행”하는 추상화에 더 가까움  
  관련 문서: [std.Io](https://ziglang.org/documentation/master/std/#std.Io)

- 다음 예시가 흥미로움  
  ```zig
  var a_future = io.async(saveFile, .{io, data, "saveA.txt"});
  var b_future = io.async(saveFile, .{io, data, "saveB.txt"});
  const a_result = a_future.await(io);
  const b_result = b_future.await(io);
  ```  
  Rust나 Python에서는 코루틴이 **await되지 않으면 진행되지 않음**.  
  반면 Zig의 예시는 `io.async`가 자체적으로 진행된다면, 이는 **태스크 생성**과 유사함.  
  이는 유효한 설계지만 다른 언어들이 택한 방향은 아님
  - C#도 비슷하게 동작함. `async` 함수가 **yield 전까지는 호출 스레드에서 실행**됨
  - Zig에서도 마찬가지로 `.await(io)`를 호출해야 실행이 보장됨.  
    즉시 실행될지, 스레드 풀에 큐잉될지는 **Io 런타임 구현**에 따라 다름
  - 실제로는 `await` 시점에 실행이 진행됨.  
    evented io의 경우 두 작업이 **교차(interleaved)** 실행될 수 있고, threaded io에서는 백그라운드에서 진행될 수도 있음.  
    즉, “어딘가에서 몰래 실행되는 태스크”는 없음
  - JavaScript도 이런 방식으로 동작함

- Go를 매일 쓰는 입장에서 Zig의 Io가 Go의 여러 **단점을 교정**한다고 느낌.  
  다만 Zig에 **채널(channel)** 개념이 있는지 궁금함.  
  Go에서는 select 키워드가 있지만 소켓에는 쓸 수 없다는 점이 늘 아쉬웠음
  - 모든 IO를 채널로 감싸면 **비용이 크다**는 점을 지적함.  
    Go의 채널은 수십 사이클 단위의 오버헤드가 있으므로, 작은 단위의 IO에는 비효율적임.  
    대신 **큰 단위의 데이터 이동**이나 **다대다 동기화**에는 유용함
  - Zig에는 Go의 채널과 유사한 [`std.Io.Queue`](https://ziglang.org/documentation/master/std/#std.Io.Queue)가 있음.  
    select 문도 비슷하게 구현 가능하지만 문법적으로는 덜 **인체공학적(ergonomic)** 임.  
    대신 **GC 없이 다양한 IO 런타임**에서 동작할 수 있다는 장점이 있음
  - Odin 언어를 써봤는지 묻고 싶음. Zig보다 Go에서 더 많은 영감을 받은 “better C”임
  - C#의 async/await처럼 **색칠된 함수**를 강제하지 않은 점이 마음에 듦.  
    Zig의 “colorless” 접근이 훨씬 낫다고 생각함
  - Go의 concurrency 모델이 특별하다고 착각하는 건 문제임.  
    Goroutine은 **그린 스레드**, 채널은 **스레드 안전 큐**일 뿐이며, Zig도 이미 이를 표준 라이브러리로 제공함

- Zig의 async 버전 Io는 Go의 접근과 거의 동일해 보임.  
  다만 Go에서는 C 라이브러리 호출 시 **스택 할당 비용**이 크고, **직접 syscall**은 플랫폼 호환성 문제가 있음.  
  Zig는 이를 **구성 가능하게 만들어** 코드 변경 없이 다양한 트레이드오프를 선택할 수 있게 한 듯함

- 새 async IO는 단순한 예제에서는 훌륭하지만, **서버 수준의 복잡한 IO**에서는 한계가 있을 수 있음.  
  관련 이슈를 [GitHub](https://github.com/ziglang/zig/issues/26056)에 올려둠

- 핵심 문제는 언어나 라이브러리 설계자가 **서로 다른 실행 컨텍스트(sync/async)** 를 연결할 수단을 제공해야 한다는 점임.  
  이를 위해 FSM(유한 상태 기계)으로 컨텍스트를 감싸고, 양쪽 간의 **통신 채널**을 제공하는 방식이 필요함  
  관련 글: [Function colors represent different execution contexts](https://danieltan.weblog.lol/2025/08/function-colors-represent-different-execution-contexts)
