1. 同步 (Synchronous) vs 异步 (Asynchronous)

  • 同步:同步意味着在发出一个调用时,调用方必须等待这个调用完成,然后才能继续执行后续的代码。在网络编程中,这意味着发出 I/O 操作时(例如 readwrite),程序会等待操作完成(即等到数据读取/写入完成后才会返回),后续的代码只有在 I/O 操作完成后才能继续执行。

  • 异步:与同步相反,异步意味着在发出一个调用后,调用立即返回,调用方无需等待操作完成就可以继续执行后续代码。I/O 操作通常会在后台进行,操作完成后再通过通知机制(比如回调函数、信号或事件)告知调用方。比如在网络编程中,使用 selectpollepoll 或 IOCP 就是一种异步编程。

例子:

  • 同步:你去银行取钱,必须等银行处理完毕并给你钱后,才能去做别的事情。

  • 异步:你去银行取钱,先给银行员递交材料,然后可以去做别的事情,等银行完成后,会通过短信通知你去取钱。

2. 阻塞 (Blocking) vs 非阻塞 (Non-blocking)

  • 阻塞:阻塞操作意味着调用线程会被挂起,直到操作完成为止。在阻塞模式下,CPU 会等待,直到 I/O 操作完成,期间不做任何其他工作。大多数的同步操作默认都是阻塞的。

  • 非阻塞:非阻塞操作意味着调用线程在发起操作后,不会等待操作完成,立即返回。如果操作没有立即完成(比如没有数据可以读取),函数会返回一个错误或者标识符(比如 EWOULDBLOCK),表明当前操作未能完成。程序可以继续执行其他操作,而不需要等待当前操作结束。

例子:

  • 阻塞:你打电话给一个客服,客服让你等一下,直到他完成查询操作,你一直等待不能挂断,直到操作结束。

  • 非阻塞:你打电话给客服,客服让你稍等,但你可以选择挂掉电话,之后他们完成查询会再给你打回来。

3. 两者之间的关系

- 同步 vs 阻塞的关系

同步和阻塞经常同时出现,但它们不是完全相同的概念。同步强调的是调用过程,阻塞强调的是线程状态

  • 同步阻塞:最常见的组合。比如网络 I/O 操作,程序发出一个请求并等待结果,在此期间线程挂起,直到 I/O 操作完成。

    • 例子:调用 recv() 函数时,直到读取到数据或遇到错误,程序才会继续执行后续代码。

  • 同步非阻塞:同步的调用依然要等结果完成,但调用时线程不会被挂起,可以继续做其他工作,直到结果准备好。

    • 例子:程序调用 select() 轮询 I/O 事件,等待直到有数据可读,但期间程序可以查询状态或执行其他任务。

- 异步 vs 非阻塞的关系

异步和非阻塞也常常配合使用,但也有区别。

  • 异步非阻塞:典型的异步操作是非阻塞的,调用后立即返回,程序不需要等待操作完成,通过某种机制(如回调、事件通知等)来处理结果。

    • 例子:WSARecv() 异步接收数据,调用立即返回,操作完成后通过 IOCP 机制通知结果。

  • 异步阻塞:虽然少见,但理论上也可以出现,异步操作发出后,仍然可以选择阻塞线程,直到操作完成。

    • 例子:虽然 select() 是异步轮询机制,但调用时如果没有事件发生,线程可能仍然会阻塞等待。

4. 组合与实际应用

  • 同步阻塞:这是最常见的网络编程模式,也是最简单的模型。像 read()write() 这些系统调用默认都是同步阻塞的。

    • 优点:编程简单,逻辑清晰。

    • 缺点:效率低,因为线程会被挂起,浪费了 CPU 资源。

  • 同步非阻塞:通常是通过轮询机制实现,比如使用 select()poll() 在等待 I/O 完成时,不会挂起线程,而是让程序可以轮询多个文件描述符。

    • 优点:可以在等待期间继续处理其他任务。

    • 缺点:编程复杂度增加,CPU 资源利用率不高(忙等待或轮询会消耗资源)。

  • 异步非阻塞:这是高性能服务器常用的模型,IOCP、epollkqueue 等提供了非阻塞的 I/O 操作,允许程序在处理其他任务的同时等待 I/O 事件,只有在 I/O 完成时才处理结果。

    • 优点:高效利用 CPU 资源,适合处理大量并发连接的场景。

    • 缺点:编程复杂度最高,尤其是在处理多种事件(接收、发送、超时等)时。

epoll 和 IOCP的对比

IOCP 和 epoll 都属于高效的 I/O 多路复用机制,主要用于处理大量并发连接的网络应用。然而,它们的运作机制不同,因此给人带来了“同步”与“异步”的理解差异。

1. IOCP 是异步的

  • IOCP(I/O Completion Ports) 是 Windows 上的一种异步 I/O 模型。

  • 异步的意思是当程序发起 I/O 操作(如 WSARecvAcceptEx)后,它不需要等待 I/O 操作完成,调用会立即返回,I/O 操作会在后台进行。当操作完成时,IOCP 会通过回调事件通知的方式告知程序,这种行为使得程序在等待 I/O 的同时可以继续做其他事情。

  • IOCP 是真正的异步模型:I/O 操作是在操作系统内核层完成的,程序并不需要在 I/O 上主动等待任何事件,内核会通过 I/O 完成端口通知线程,告诉它们操作已完成。

2. epoll 是同步的,但与异步搭配使用

  • epoll 是 Linux 上的 I/O 多路复用机制,尽管很多人认为它是异步的,但从严格意义上讲,epoll 本身是同步的

    • 当你调用 epoll_wait 时,程序会同步等待 I/O 事件发生。同步意味着线程会阻塞在 epoll_wait 上,直到某个文件描述符有 I/O 事件(例如,准备好读取或写入)。

    • 一旦有事件触发,epoll_wait 返回,你的程序还需要手动调用 read()write() 等操作来处理具体的 I/O 任务。这就是为什么很多人会说 epoll 是同步非阻塞的,因为你还是需要自己去调用 I/O 操作,而不像 IOCP 那样内核直接通知你结果。

  • 异步搭配:epoll 可以与异步 I/O 操作结合使用。比如,配合 O_NONBLOCK 模式的文件描述符,程序不会被 I/O 操作阻塞,从而实现非阻塞异步。然而,epoll 仅负责事件通知,不负责真正的异步 I/O 操作。

3. 同步与异步的区别

  • IOCP 的异步:你发起 I/O 请求时,不需要阻塞线程等待,内核在完成 I/O 操作后会通知你,I/O 操作本身也是异步完成的。

  • epoll 的同步:虽然 epoll 可以通知哪些文件描述符有事件发生,但它并不会自动完成 I/O 操作,你还是要自己调用 readwrite,这部分操作是同步的。epoll 仅仅提供了一个等待事件发生的机制。

4. epoll 的“同步非阻塞”

  • 尽管 epoll 的 epoll_wait 是一个阻塞调用,它也能配置为非阻塞模式

    • 在非阻塞模式下,你可以使用 O_NONBLOCK 标志来使文件描述符变为非阻塞,同时使用 epoll 来监听多个文件描述符的事件。这样,程序就不会因为一个 I/O 操作(如 readwrite)而被阻塞。

    • 这种方式通常被称为“同步非阻塞”,因为虽然 readwrite 操作依然是同步的,但你可以避免线程因为这些操作而被阻塞。

总结

  • IOCP 是异步 I/O 的代表,I/O 操作和通知机制都在内核中异步处理。

  • epoll 是同步非阻塞 I/O 的代表,程序仍需同步调用 I/O 操作,但通过事件通知机制避免了单个 I/O 操作阻塞线程,提升了并发处理能力。

尽管 epoll 是同步模型,它能很好地与异步机制搭配使用,从而实现高效的 I/O 处理。