第二章:同步 I/O (Sync I/O)

第二章:同步 I/O (Sync I/O)

在扩展到分布式之前,我们先来弄明白单机 IO 的手段。同步/异步/Poller/线程池,眼花缭乱的名词,是否在故弄玄虚?

我工作使用的第一门编程语言是 Go,享受了大量原生 Goroutine、GMP 调度器的便利,我以为编程语言天生具备并发能力是一件很自然的事情。

从事分布式存储工作后,发觉有经验的同事在做系统设计时,一定会重点关注 IO 和线程模型。IO 包括用户请求到本机 IO 整个链路。而线程模型和 IO 是否阻塞、负载和性能需求息息相关。必须掌握这些,才能针对不同的用户需求设计出最佳的存储系统。

这也迫使我从系统编程语言的角度重新思考、实践了一些常见的 IO 模式。这个过程反而令我更加理解了 Goroutine 和 Go Runtime 的设计动机、了解了更多 syscall 的基础概念。

本文以小型原型为线索,记录了笔者在此主题上的所见所闻。受限于笔者的经验,本文讨论的 IO 模型以及代码实验限定在 Linux 平台上。如有谬误,感谢读者交流、指正!

Linux IO 可根据以下性质分类1

  • 是否被 Kernel Page Cache 缓存(📦 Buffered/🎯 Direct
  • 是否会发生阻塞 (⏳ Sync/⚡Aysnc)
I/O Type ⏳ Sync I/O (Blocking) ⚡ Async I/O (Non-blocking)
📦 Buffered I/O read(), write() io_uring, libaio
🎯 Direct I/O read(), write() with O_DIRECT flag io_uring, libaio with O_DIRECT flag

你可以根据自己的需求选择任意一种、甚至多种合适的方式一起使用,比如写时候使用 Sync + Bufferd I/O,读的时候使用 Async + Direct I/O

如果你是新手开发者,可以暂时搁置所有 async 相关的疑惑。不要急,今天就让我们只在 同步 IO (Sync I/O) 的世界四处看看!

参考资料