- Zig의 새로운 비동기 I/O 인터페이스 도입으로, I/O 구현 방식을 호출자가 직접 선택 및 주입 가능함
- 새롭게 설계된 Io 인터페이스는 동시에 비동기성과 병렬성을 지원하며, 코드 재사용성과 최적화에 집중함
- Blocking I/O, 이벤트 루프, 스레드 풀, 그린 스레드, 스택리스 코루틴 등 다양한 표준 라이브러리 구현체를 제공할 예정임
- 새로운 API를 통해 미래 취소 및 리소스 관리, 버퍼링 및 세분화된 입출력 동작 가능함
- 기존의 함수 컬러링 문제를 해결하여, 하나의 라이브러리로 동기/비동기 운영 모두 최적화 가능하게 됨
개요
Zig는 최근 새로운 비동기 I/O 인터페이스를 설계함으로써, I/O 작업의 유연성 및 병렬성 지원에 중점을 두는 방향으로 발전하고 있음. 이번 변화는 기존 async/await 패러다임을 분리하여, 실제 프로그램 작성자가 더욱 다양한 I/O 전략을 채택할 수 있도록 설계되었음.
새로운 I/O 인터페이스
이전에는 I/O 관련 객체들을 코드 내에서 직접 생성 및 사용하였으나, 이제는 Io 인터페이스를 호출자가 주입하도록 변경됨.
- 이 방식은 Allocator 패턴과 유사하게, 호출 측에서 I/O 구체 구현을 선택 및 주입함
- 외부 패키지 코드에도 일관된 방식으로 I/O 전략을 적용할 수 있음
주요 변화
- Io 인터페이스는 이제 동시성(concurrency) 연산도 담당함
- 코드가 동시성을 올바르게 표현할 경우, Io의 구현체에 따라 병렬성(parallelism) 제공 가능
예제 코드
- 동시성이 없는 (직렬) 코드와, io.async 및 await로 병렬 가능성이 표현된 코드 두 가지를 비교함
- 직렬 코드: 두 파일에 차례로 저장, 병렬성 기회 활용 불가
- 병렬 코드: futures를 활용한 파일 저장, 비동기 이벤트루프에서 더 효율적으로 동작
await와 try의 조합
- await와 try를 함께 사용하면, 하나의 future에서 에러 발생 시 다른 future의 리소스를 반납하지 못하는 문제 존재
- defer 및 future.cancel로 적절히 취소 및 정리를 명확히 할 수 있음
Future.cancel API
- Future.cancel()과 Future.await()는 idempotent(여러번 호출해도 부작용 없음)함
- 이미 완료된 future에 cancel을 호출하면 자원만 해제되고, 완료되지 않은 작업은 error.Canceled 반환
표준 라이브러리 I/O 구현
Io 인터페이스는 런타임 다형성 기반 인터페이스로, 직접 구현하거나 서드파티 패키지의 구현을 사용할 수 있음. Zig의 표준 라이브러리는 다양한 유형의 I/O 구현을 제공할 계획임.
- Blocking I/O: 단순히 기존 C 스타일 blocking 입출력을 사용, 추가 오버헤드 없음
- 스레드 풀: Blocking I/O들을 OS 스레드 풀에 분산, 병렬성 일부 도입. 네트워크 클라이언트 등에서는 최적화가 필요
- 그린 스레드: Linux의 io_uring 등 비동기 시스템 콜을 활용, OS 스레드에서 여러 그린(경량) 쓰레드를 처리. 플랫폼 지원 필요(x86_64 Linux 우선)
- 스택리스 코루틴: 명시적 스택 필요 없는 상태 머신 기반의 코루틴. WASM 등 일부 플랫폼 호환 목적. Zig 컴파일러의 프로퍼티브 컨벤션 재도입 필요
설계 목표
코드 재사용성
비동기 I/O의 가장 큰 이슈는 코드 재사용성이며, 타 언어에서는 blocking/async 함수가 별도로 존재하여 코드가 분리되는 문제가 있음. Zig의 방식은
- 하나의 라이브러리가 동기 및 비동기 모드를 모두 효과적으로 지원
- async/await가 ‘함수 컬러링’ 현상을 제거하고, Io 시스템을 통해 런타임에서도 다양한 실행모델에 종속적이지 않음
결론적으로 함수 컬러링 문제를 완전히 해결
최적화
- 새로운 Io 인터페이스는 비제네릭, vtable 기반 가상 호출 방식으로 구현됨
- 가상 호출은 코드 팽창을 줄이지만, 실행 시 약간의 오버헤드가 있음. 최적화 빌드에서는 단일 Io 구현이면 de-virtualization(가상 호출 제거) 가능
- 여러 Io 구현 사용시엔 가상 호출 유지(코드 중복 방지 목적)
버퍼링 전략
- 기존에는 각 구현체(reader/writer)가 버퍼링을 담당했으나, 이제는 Reader와 Writer 인터페이스 레벨에서 버퍼링을 수행
- 버퍼 flush 외에는 가상 호출 경로를 거치지 않아 최적화 용이
의미론적 I/O 연산
Writer 인터페이스는 특정 최적화 연산을 위한 두 가지 새로운 프리미티브 제공
- sendFile: POSIX sendfile에서 영감을 받아, 파일 디스크립터 간 데이터 이동을 커널 내에서 처리. 메모리 복사 최소화
- drain: Vectorized write + splatting 지원. 여러 데이터 세그먼트 일괄 전송, writev 시스템 콜로 변환 가능. splat 파라미터로 마지막 요소 반복 활용 가능(압축 등 스트림에서 활용)
로드맵
이 변화의 일부는 Zig 0.15.0부터 도입되나, 라이브러리 대대적 개편이 필요하여 전체 도입은 차기 릴리즈를 기다려야 함. SSL/TLS, HTTP server/client 등 주요 모듈도 새 Io 시스템으로 재설계 예정
FAQ
Q: Zig는 로우레벨 언어인데 왜 async가 중요한가?
- Zig는 견고함, 최적화, 재사용성을 지향
- Non-blocking 입출력을 표준화함으로써, 타 라이브러리·서드파티 코드도 전체 I/O 전략에 맞게 조정 및 재사용성 확보
Q: 패키지 저자들이 이제 async를 모든 코드에 활용해야 하나?
- 아님. 모든 코드가 동시성을 표현할 필요 없음
- 일반적인 순차적 코드도 사용자가 선택한 I/O 전략에 맞게 동작함
Q: 어떤 실행 모델이든 플러그인만 하면 무조건 정상 동작하나?
- 대부분은 네
- 단, 코드상의 프로그래밍 오류(예: 동시작업 요건 충족 안 함)는 정상 동작 불가
실행 예시와 함께, 비동기성과 병렬성의 차이, 올바른 동작 흐름 설계 필요성 언급
결론
Zig는 새로운 Io 인터페이스 도입으로 입출력 전략의 선택 유연성, 코드 재사용성, 최적화 가능성을 크게 높였음. 이로써 비동기/동기 기반의 함수 작성 제약 없이, 개발자는 동시성·병렬성 구조를 더 명확하게 표현하고 각종 플랫폼·실행 모델에도 효과적으로 대응할 수 있게 됨.