同步、异步IO,以及和阻塞、非阻塞IO的区别和联系
阻塞 (Blocking) vs. 非阻塞 (Non-blocking)
这个维度描述的是应用程序(你)在发起一个I/O请求(点咖啡)后,如果数据(咖啡)还没准备好,应用程序(你)会不会被卡住(原地等待)。它关注的是单个系统调用的行为。
1. 阻塞I/O (Blocking I/O)
- 定义:应用程序发起I/O请求后,如果内核数据还没准备好,那么应用程序的这个线程就会被挂起(阻塞),直到数据准备好并将数据从内核拷贝到用户空间后,请求才会返回。
-
咖啡店比喻: 你走到柜台点了一杯咖啡。服务员告诉你:“好的,请稍等。” 然后你就站在柜台前,什么也不干,一直等到服务员把做好的咖啡递给你,你才离开柜台去做别的事情。在等待的整个过程中,你被“阻塞”在了柜台前。
你 -> 点咖啡() // 发起请求,然后就一直卡在这里... // ...直到咖啡做好拿到手 你 -> 喝咖啡() // 请求返回,继续做后面的事* 优点:编程模型最简单,逻辑清晰。 * 缺点:一个线程在I/O期间完全被挂起,无法做任何其他事情,导致资源利用率低。
2. 非阻塞I/O (Non-blocking I/O)
- 定义:应用程序发起I/O请求后,如果内核数据还没准备好,该请求会立即返回一个错误码(例如
EWOULDBLOCK)。应用程序的线程不会被挂起,可以继续执行其他任务。应用程序需要通过轮询(polling)的方式,不断地去询问内核数据是否准备好了。 -
咖啡店比喻: 你走到柜台点了一杯咖啡。服务员看了一眼,发现还没好,立刻告诉你:“还没好呢!” 然后你就离开了柜台。但你心里惦记着咖啡,所以你每隔一小会儿就跑回柜台问:“我的咖啡好了吗?”。服务员每次都立刻回答你“好了”或“没好”。在你一次次询问的间隙,你可以玩手机或者做别的事。直到有一次你问的时候,服务员把做好的咖啡给了你。
while (true) { 咖啡 = 点咖啡() // 发起请求,立刻返回 if (咖啡 != "还没好") { break // 拿到咖啡了,跳出循环 } // 在这里可以做点别的事... 玩手机() } 你 -> 喝咖啡()* 优点:线程不会被阻塞,可以在等待I/O的间隙处理其他任务。 * 缺点:需要不断地轮询,这会持续消耗CPU资源(“忙等待”)。
同步 (Synchronous) vs. 异步 (Asynchronous)
这个维度描述的是应用程序(你)与操作系统(服务员)之间协作完成整个I/O操作(从点单到拿到咖啡的全过程)的模式。它关注的是整个I/O事件的通知模型。
3. 同步I/O (Synchronous I/O)
- 定义:应用程序自己主动发起I/O操作,并且需要自己全程关心这个操作的状态,直到操作最终完成(数据成功从内核拷贝到用户空间)。
- 核心特征:I/O操作的发起方和最终数据处理方是同一个线程。
-
咖啡店比喻: 无论是阻塞(你傻等在柜台)还是非阻塞(你反复跑去问),都是你自己全程在跟进“要咖啡”这件事。你必须亲自等到或者问到咖啡好了,然后亲自从服务员手里接过来。整个过程,你(应用程序)是主动方。
因此,阻塞I/O和非阻塞I/O都属于同步I/O的范畴。
4. 异步I/O (Asynchronous I/O)
- 定义:应用程序发起一个I/O请求后,便将这件事完全委托给操作系统了,然后立即返回,去做其他的事情。当操作系统完成了所有工作(包括数据准备和从内核拷贝到用户空间),它会通过某种机制(如回调函数、事件通知)来通知应用程序。
- 核心特征:I/O操作的发起和后续的数据处理可以由不同的线程(或机制)完成。
-
咖啡店比喻: 你走到柜台点了一杯咖啡,并且给了服务员一个震动的取餐器。然后你就彻底忘了这件事,找个座位去处理工作了。整个做咖啡、把咖啡从吧台端出来的过程都由服务员(操作系统)全权负责。当咖啡完全准备好后,服务员按了一下按钮,你的取餐器响了,这时你才过去把咖啡取走。
``` // 告诉服务员:咖啡好了就震动我的取餐器(callback) 点咖啡(取餐器) 你 -> 处理工作() // 完全不管咖啡的事,去做别的工作
// ...一段时间后... // (取餐器震动) -> 触发回调 function on_coffee_ready(咖啡) { 喝咖啡() } ``` * 优点:线程完全不会被I/O操作阻塞,资源利用率极高。一个线程可以同时处理大量的I/O请求。 * 缺点:编程模型最复杂,需要事件驱动和回调机制。
区别
-
维度的不同:
- 阻塞或非阻塞,关注的是调用API后,线程是否会立刻挂起。
- 同步或异步,关注的是整个I/O流程中,由谁来负责跟进和通知结果。
-
核心区别:
- 同步与异步:最本质的区别在于,同步I/O需要应用程序主动去轮询或等待I/O操作的结果;而异步I/O是应用程序被动地接收I/O操作完成的通知。
- 非阻塞与异步:这是最容易混淆的。
- 非阻塞I/O (属于同步):应用需要多次主动询问内核: "好了没?"。虽然线程没阻塞,但它得一直操心这件事。
- 异步I/O:应用只需告诉内核: "你弄吧,好了叫我。"。应用完全不用操心进度,只需等待通知。
I/O多路复用 (Multiplexing I/O):
它通常与非阻塞I/O配合使用,是实现高性能NIO的关键。它引入了一个“代理”(如 select, epoll),你可以把很多路I/O请求都注册给这个代理。然后你的线程只需要阻塞地问这个“代理”:“我托你办的那些事,有任何一件好了吗?”。这样就避免了对每一个I/O都进行无效轮询,大大提高了效率。这仍然属于同步I/O,因为最终还是需要你的线程主动去问代理,并自己去拷贝数据。