swift-io
一个用于 Swift 的高性能异步 I/O 执行器。通过专用工作线程、有界队列和确定性的关闭语义,将阻塞系统调用与 Swift 的协作式线程池隔离开来。
主要特性
- 专用线程池 - 阻塞 I/O 永远不会耗尽 Swift 的协作式执行器
- 基于上下文的完成 - 消除字典查找;任务携带其自身的 continuation
- 基于状态转换的信号 - 仅在空→非空时发出信号;每次唤醒时排空批次
- 仅移动资源 - 针对
~Copyable泛型化,具有类型安全的槽位传输 - 端到端类型化抛出 - API 表面没有
any Error - Swift 6 严格并发 - 完全符合
Sendable规范,零数据竞争
设计哲学
swift-io 专为基础设施代码设计,在这些场景中,正确性、确定性和资源边界比绝对峰值吞吐量更重要。它有意以无界排队换取负载下的可预测行为。
非目标: swift-io 不旨在通过无界排队来最大化吞吐量。
性能
比较 swift-io 与 SwiftNIO 的 NIOThreadPool 的基准测试(release 模式,arm64,Apple M1)。报告了中位数;在尾部延迟差异显著时显示 p95/p99。
基准测试模拟短阻塞工作负载(每个 10µs)。实际 I/O 由系统调用主导,从而减少了相对开销差异。这些基准测试隔离了执行器行为;它们不模拟端到端 I/O 延迟。
吞吐量
| 基准测试 | swift-io | NIOThreadPool | 差异 |
|---|---|---|---|
| 顺序执行 (1000 × 10µs 操作) | 4.51ms | 7.15ms | 快 37% |
| 并发执行 (1000 × 10µs 操作) | 1.72ms | 1.43ms | NIO 快 17% |
开销 (每操作,中位数)
| 基准测试 | swift-io | NIOThreadPool | 差异 |
|---|---|---|---|
| 线程调度 | 4.00µs | 7.88µs | 快 49% |
| 成功路径 | 3.96µs | 7.83µs | 快 49% |
| 失败路径 | 4.46µs | 10.79µs | 快 59% |
| 队列准入 | 4.13µs | 7.83µs | 快 47% |
竞争
| 场景 | swift-io | swift-io sharded | NIOThreadPool | 备注 |
|---|---|---|---|---|
| 中等 (10:1) | 216µs | 224µs | 182µs | NIO 中位数胜出;p95 swift-io 253µs 对比 NIO 671µs |
| 高 (100:1) | 1.01ms | 568µs | 632µs | Sharded 比 NIO 快 10% |
| 极高 (1000:1) | 3.45ms | 2.54ms | 2.55ms | Sharded 与 NIO 持平 |
设计优势
| 机制 | 收益 | 测量结果 |
|---|---|---|
| 基于上下文的完成 | 消除共享字典查找 | 83ns 对比 1.50µs (18×) |
| 分片通道 | 降低负载下的锁竞争 | 在 100:1 下比分片前快 40% |
总结
swift-io 优先考虑可预测的延迟、有界的资源使用和确定性的关闭,而非峰值吞吐量。在具有无界队列的高并发工作负载下,NIOThreadPool 仍具有优势;swift-io 在负载下表现出更低的每操作开销和更稳定的尾部行为。
何时使用 swift-io 而非 NIOThreadPool
- 使用 swift-io 当你需要有界队列、确定性关闭、类型化错误保留或可预测的尾部延迟时。
- 使用 NIOThreadPool 当你希望获得最大并发吞吐量并接受无界队列语义时。
为什么选择 swift-io?
从概念上讲,swift-io 是一个由 actor 管理的独占资源池,运行在有界的阻塞通道上。
Swift 的协作线程池专为快速、非阻塞的工作而设计。当你混入阻塞系统调用时:
| 问题 | 协作线程池 | swift-io |
|---|---|---|
| 阻塞系统调用 | 使协作线程饥饿 | 专用线程隔离阻塞工作 |
| 等待者管理 | 手动处理 continuation | 具有背压的有界 FIFO 队列 |
| 资源清理 | 手动,易出错 | 确定性拆除策略 |
| 取消 | 语义不一致 | 定义明确:接受前/接受后 |
| 仅移动资源 | 无原生支持 | 基于 ~Copyable 的泛型,采用槽位模式 |
| 错误处理 | 无类型 throws | 带有 IO.Lifecycle.Error<IO.Error<E>> 的类型化 throws |
安装
dependencies: [
.package(url: "https://github.com/swift-foundations/swift-io.git", from: "0.2.0")
]
.target(
name: "YourTarget",
dependencies: [
.product(name: "IO", package: "swift-io"),
]
)
要求:
- Swift 6.3.1+ (swift-tools-version: 6.3.1)
- Apple 平台:macOS 26 / iOS 26 / tvOS 26 / watchOS 26
- Linux 和 Windows:参见 平台支持
快速开始
一次性阻塞操作
最简单的模式——在专用线程上运行阻塞操作:
import IO
let pool = IO.Executor.Pool<Void>()
let data = try await pool.run {
try blockingSyscall() // Runs on dedicated thread, not cooperative pool
}
await pool.shutdown()
托管资源
对于长生命周期资源(文件句柄、连接),请注册它们并使用事务:
import IO
let pool = IO.Executor.Pool<FileHandle>()
// Register → get ID
let id = try await pool.register {
try FileHandle.open(path)
}
// Transaction → exclusive access
let data = try await pool.transaction(id) { handle in
try handle.read()
}
// Destroy → cleanup
try pool.destroy(id)
await pool.shutdown()
领域门面模式
在生产环境中,将池封装在特定领域的 API 中(参见 swift-file-system):
public actor FileSystem {
private let pool: IO.Executor.Pool<FileHandle>
public init() { self.pool = IO.Executor.Pool() }
public func read(
at path: String
) async throws(IO.Lifecycle.Error<IO.Error<ReadError>>) -> Data {
try await pool.run {
try Data(contentsOfFile: path)
}
}
public func shutdown() async { await pool.shutdown() }
}
错误处理
swift-io 使用类型化抛出。池方法抛出 IO.Lifecycle.Error<IO.Error<E>>,您可以对其进行穷举模式匹配:
do {
let value = try await pool.run {
try myOperation() // throws MyError
}
} catch {
switch error {
case .shutdownInProgress:
// Pool is shutting down
case .cancellation:
// Task was cancelled
case .failure(let ioError):
switch ioError {
case .leaf(let myError):
// myError is MyError (typed!)
case .handle(let handleError):
// e.g. .notFound, .scopeMismatch
case .executor(let execError):
// e.g. .waiterQueueFull
case .lane(let laneError):
// Lane infrastructure error
}
}
}
错误模型
swift-io 端到端使用类型化抛出。公共 API 不会抛出 any Error。操作错误被保留为 E 并提升为 IO.Lifecycle.Error<IO.Error<E>>。
错误层次结构:
IO.Lifecycle.Error<E>
├── .shutdownInProgress // Lifecycle: pool shutting down
├── .cancelled // Lifecycle: task cancelled
└── .failure(E) // Wraps operational errors
└── IO.Error<Leaf>
├── .leaf(Leaf) // Your operation's error type
├── .handle(...) // Handle errors (.notFound, .scopeMismatch)
├── .executor(...) // Executor errors (.waiterQueueFull)
└── .lane(...) // Lane errors (.queue(.full), .overloaded)
架构
┌─────────────────────────────────────────────┐
│ IO │ ← Pool, Handle.ID, Error
├─────────────────────────────────────────────┤
│ IO Blocking │ ← Lane abstraction
├─────────────────────────────────────────────┤
│ IO Blocking Threads │ ← Thread pool + signal optimization
├─────────────────────────────────────────────┤
│ IO Primitives │ ← Core types, platform abstraction
└─────────────────────────────────────────────┘
主要类型
| 类型 | 用途 |
|---|---|
IO.Executor.Pool<Resource> | 基于 Actor 的资源池,支持事务访问 |
IO.Handle.ID | 已注册资源的范围标识符 |
IO.Blocking.Lane | 执行后端(.threads() 或 .sharded()) |
IO.Lifecycle.Error<E> | 生命周期封装(关闭、取消) |
IO.Error<E> | 保留操作错误的类型化错误 |
执行模型
Swift Task Lane (Thread Pool)
│ │
├─── run(operation) ──────────►│
│ (suspends) │
│ ├─── execute on worker thread
│ │
│◄── resume with result ───────┤
│ (context-based, no lookup)│
设计细节
信号优化
工作线程使用基于状态转换的信号机制以最小化内核开销:
- 休眠者跟踪 - 仅当工作线程实际处于等待状态时才发送信号
- 空→非空转换 - 每个批次仅发送一次信号,而非每个任务
- 排空循环 - 每个唤醒周期最多处理 16 个任务
与按任务发送信号相比,这消除了约 90% 的虚假 pthread_cond_signal 调用。
基于上下文的完成
任务携带其完成上下文,从而消除了共享字典状态和持锁哈希操作:
// Traditional: O(1) amortized but with hash overhead + lock contention
completions[ticket] = result // store under lock
let result = completions.removeValue(forKey: ticket) // lookup under lock
// swift-io: Direct pointer, zero lookup, no shared dictionary
do { try job.context.complete(with: result) } catch {} // 83ns, atomic CAS
保证
swift-io 保证的内容:
- 恰好一次的 continuation 恢复
- 通过容量受限的队列实现有界内存
- 带有在途完成的确定性关闭
- 取消安全性
公平性:
- 队列顺序为 FIFO
- 在竞争条件下调度为尽力而为
- 不保证完成顺序(drain 循环可能会重新排序)
swift-io 不保证的内容:
- 接受后的系统调用中断
- 高竞争条件下的严格 FIFO 完成
- 跨进程协调
配置
// Custom thread pool
let pool = IO.Executor.Pool<MyResource>(
lane: .threads(.init(count: 4, queueLimit: 128)),
handleWaitersLimit: 32
)
// Sharded lane for reduced contention
let pool = IO.Executor.Pool<MyResource>(
lane: .sharded(count: 4)
)
// Custom teardown
let pool = IO.Executor.Pool<FileHandle>(
teardown: .run { handle in
try? handle.close()
}
)
平台支持
CI 目标为 macOS、Linux 和 Windows;请参阅 workflow runs 以了解当前状态。
| 平台 | CI 目标 | 备注 |
|---|---|---|
| macOS | Swift 6.3.1, debug | 在 CI 矩阵中指定 |
| Linux (Ubuntu) | Swift 6.3.1, release | 在 CI 矩阵中指定 |
| Windows | Swift 6.3.1 | 在 CI 矩阵中指定 |
| iOS/tvOS/watchOS | — | 支持(与 macOS 使用相同的代码库) |
相关包
- swift-file-system - 基于 swift-io 构建的文件系统操作
- swift-time-standard - 用于截止时间的类型
状态与维护者
此包为公开 Alpha 版(1.0 之前):接口正在稳定中,API 可能会在次要版本之间发生变化。
由 Coen ten Thije Boonkkamp 维护 — 提供 Swift 基础设施和文档系统咨询服务:coen@coenttb.com。
许可证
Apache 2.0 - 详见 LICENSE。