1 单线程、任意创建线程与线程池的对比
分别使用单线程、任意创建线程的多线程以及线程池的方式实现一个简单的 HTTP Server,使用 Super Benchmark 对其进行压力测试。
单线程
任意创建线程
使用固定大小的线程池
2 NIO 模型与相关概念
通信模型
阻塞、非阻塞:可以理解为做一件事情能否立即得到返回结果,能立即得到就是非阻塞,不能立即得到就是阻塞。
同步、异步:完成一系列任务,如果总是先做一件事情,再做下一件事情就是同步模型;可以同时做几件事情,不用等一件事做完再去做另一件事,就是异步模型。
有什么关系和区别?
- 同步异步是通信模式。
- 阻塞、非阻塞是线程处理模式。
五种IO 模型
我们的应用程序是在用户空间中运行的,它不存在实质的 IO 过程,真正的 IO 是在操作系统中进行的。针对网络IO的操作,可以分成两个阶段,IO 调用阶段和 IO 执行阶段。
- IO 调用是由进程向操作系统发送一次 IO 请求。
- IO 执行是由操作系统内核来完成的,数据准备好之后从内核缓冲区拷贝到用户进程缓冲区。
阻塞式IO、BIO
用户线程从发起读请求到读完成的这段时间一直被阻塞,线程资源被浪费。
- 优点: 实现简单。
-
缺点: 需要为每一个连接的IO配备一个线程,在高并发应用场景下,需要大量的线程来维护大量的网络连接,内存、线程切换开销会十分巨大。
非阻塞式IO
在第一阶段,用户线程采用轮询的方式询问内核数据是否准备好,内核如果没有准备好直接返回错误码,在轮询的间隙不占用线程资源。在第二阶段拷贝数据的时候,用户线程依然是阻塞的。
- 优点: 每次发起的IO系统调用,在内核等待数据过程中可以立即返回。用户线程不会阻塞,实时性较好。
- 缺点: 需要不断的进行系统调用轮询,测试数据是否准备好,将占用大量的CPU时间,效率低下。
IO 多路复用(IO multiplexing),也称事件驱动IO(event-driven IO)
在单个线程里同时监控多个套接字,通过select 或poll 轮询所负责的所有socket,当某个socket 有数据到达了,就通知用户进程。
IO 复用同非阻塞IO 本质一样,不过利用了新的select 系统调用,由内核来负责本来是请求进程该做的轮询操作。看似比非阻塞IO 还多了一个系统调用开销,不过因为可以支持多路IO,才算提高了效率。进程先是阻塞在select/poll 上,再是阻塞在读操作的第二个阶段上。
- 优点: 使用一个查询就绪状态的线程就可以同时同时轮询成千上万个连接。系统不必要创建和维护大量的线程,大大减小了系统的开销。
- 缺点: select/epoll 调用都是阻塞式的,属于同步IO。都需要在读写事件就绪后,由系统调用本身负责进行读写,也就是这个读写过程是阻塞的。
select/poll 的几大缺点:
(1)每次调用select,都需要把fd 集合从用户态拷贝到内核态,这个开销在fd 很多时会很大
(2)同时每次调用select 都需要在内核遍历传递进来的所有fd,这个开销在fd 很多时也很大
(3)select 支持的文件描述符数量太小了,默认是1024
epoll(Linux 2.5.44内核中引入,2.6内核正式引入,可被用于代替POSIX select 和poll 系统调用):
(1)内核与用户空间共享一块内存
(2)通过回调解决遍历问题
(3)fd 没有限制,可以支撑10万连接
信号驱动I/O
信号驱动IO 与BIO 和NIO 最大的区别就在于,在IO 执行的数据准备阶段,不需要轮询。
如图所示:当用户进程需要等待数据的时候,会向内核发送一个信号,告诉内核我要什么数据,然后用户进程就继续做别的事情去了,而当内核中的数据准备好之后,内核立马发给用户进程一个信号,说”数据准备好了,快来查收“,用户进程收到信号之后,立马调用 recvfrom ,去查收数据。
- 优点: 在第一阶段不使用轮询,而是使用信号的方式。
- 缺点: 第二阶段依然是阻塞的。
异步式IO
异步IO 真正实现了IO 全流程的非阻塞。用户线程发起系统调用之后立即返回,用户线程不必在这里等待。当内核将数据准备好之后,将数据从内核缓冲区拷贝到用户进程缓冲区,然后发送信号通知用户线程来处理。
- 优点: 在内核等待数据和复制数据的两个阶段,用户线程都不是阻塞的。
- 缺点: 需要操作系统底层内核提供支持。
- 本文固定链接: https://weiguangli.com/archives/601
- 转载请注明: lwg0452 于 Weiguang的博客 发表