真实如刀的洞见:NIO,epoll,多路复用,更好地理解IO

本文通过实例探讨了BIO模型中多线程处理客户端请求导致的IO阻塞问题,介绍了NIO的非阻塞机制以及C10K问题。随后讲解了如何使用select和epoll实现多路复用,以减少系统调用次数。
摘要由CSDN通过智能技术生成

再往下走发现了一个clone,这是个什么玩意呢?想必你已经猜到就是我们上面的 new Thread((),每接收到一个客户端创建了一个新的线程,线程id:16872

真实如刀的洞见:NIO,epoll,多路复用,更好地理解IO

我们可以验证下,是不是多了一个16872线程,如下图果然是多了一个16872的线程

真实如刀的洞见:NIO,epoll,多路复用,更好地理解IO

我们看下新建的这个线程,发现这个线程阻塞在recvfrom(6,这个就和理论结合了,客户端数据未到达线程一直被阻塞

真实如刀的洞见:NIO,epoll,多路复用,更好地理解IO

接下来我们在客户端给一个输入,然后再看下服务端的反应

真实如刀的洞见:NIO,epoll,多路复用,更好地理解IO

发现从6描述符中读到了内容,然后write1(前面我们说过1是标准输出),并且我们在服务端看到了客户端输入的内容

真实如刀的洞见:NIO,epoll,多路复用,更好地理解IO

真实如刀的洞见:NIO,epoll,多路复用,更好地理解IO

到这里我们比较清楚地看到io阻塞的点了,上面的例子是通过多线程来处理多客户端的请求(每个客户端对应一个线程),并且在这个过程中必然会有内核的参与(无论走的jvm自带的函数还是lib最终走的都是sys call)

上面一顿操作,下面我们稍微搞个图描述下

真实如刀的洞见:NIO,epoll,多路复用,更好地理解IO

其实这就是BIO,典型的BIO,然后这里面有什么问题呢

线程太多了,线程也是资源,创建线程会走系统调用(上文描述的clone),所以必然会发生软中断;我们知道线程栈的内存是独立的所以会造成内存资源的消耗,cpu的切换也会有浪费,其实这都是表面的问题, 根本问题其实是IO的阻塞

那么如何让它不阻塞呢,通过man socket 这个命令我们看下socket系统调用的说明;下图可以看到内核中是可以让一个socket非阻塞的( socket Linux 2.6.27内核开始支持非阻塞模式。)

真实如刀的洞见:NIO,epoll,多路复用,更好地理解IO

NIO

===

调用的时候加一个参数可以使socket非阻塞,这个时候read fd的时候就是非阻塞的了,有数据就直接读,没有数据就直接返回,这就是所谓的NIO,对于NIO其实有如下两种说法

  • 从App lib这个角度看,N是new的意思,是一个新的io体系,有channel、buffer…这些新的组件

  • 从操作系统内核的角度N是no blocking非阻塞的意思

真实如刀的洞见:NIO,epoll,多路复用,更好地理解IO

这种模型相对于前面的BIO解决了开辟很多线程的问题;好像比前面更牛逼了,那么这么做有什么问题呢?

不知道你有没有听过C10K的问题,比如说有1w个client,那么每次会循环1w次调用系统内核,看下有没有数据,也就是说每次循环会有O(n)复杂度的sys call的过程,但是可能1w次中只有几个是有数据或者说是准备就绪的,也就是绝大多数的系统调用都是白忙活的,这有点浪费资源了吧!

对于这个问题,应该怎么解决呢?是否可以将O(n)的复杂度进行一个降解,这一块还是要看内核怎么做优化的,我们可以看下select这个命令,下面关于它的描述是“ 允许一个程序监听多个文件描述符,等待一个或者多个文件描述符变成了可用状态 ”

SYNOPSIS

/* According to POSIX.1-2001, POSIX.1-2008 */

#include <sys/select.h>

/* According to earlier standards */

#include <sys/time.h>

#include <sys/types.h>

#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,

fd_set *exceptfds, struct timeval *timeout);

void FD_CLR(int fd, fd_set *set);

int FD_ISSET(int fd, fd_set *set);

void FD_SET(int fd, fd_set *set);

void FD_ZERO(fd_set *set);

#include <sys/select.h>

int pselect(int nfds, fd_set *readfds, fd_set *writefds,

fd_set *exceptfds, const struct timespec *timeout,

const sigset_t *sigmask);

Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

pselect(): _POSIX_C_SOURCE >= 200112L

DESCRIPTION

select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become “ready” for some class of I/O operation (e.g., input pos-

sible). A file descriptor is considered ready if it is possible to perform a corresponding I/O operation (e.g., read(2) without blocking, or a sufficiently small write(2)).

select() can monitor only file descriptors numbers that are less than FD_SETSIZE; poll(2) does not have this limitation. See BUGS.

The operation of select() and pselect() is identical, other than these three differences:

(i) select() uses a timeout that is a struct timeval (with seconds and microseconds), while pselect() uses a struct timespec (with seconds and nanoseconds).

(ii) select() may update the timeout argument to indicate how much time was left. pselect() does not change this argument.

通过下面的图就容易理解点,程序先调了一个叫select的系统调用,然后传入fds(如果有1w个文件描述符,在这一次的系统调用中将这1w个文件描述符发给内核),内核会返回若干个可用状态的文件描述符,最终读数据是基于这若干个可用状态的文件描述符访问内核去读,这个系统复杂度可以理解为O(m),前面是nio(每一个客户端问下是否就绪),现在是将所有的客户端连接扔到一个工具select(多路复用器)中;所以很多个客户端连接复用了一个系统调用,返回了准备就绪的连接,然后程序自己去发生读写

真实如刀的洞见:NIO,epoll,多路复用,更好地理解IO

这种多路复用的模型,相对于上面的nio系统复杂度o(n),多路复用减少了系统调用的次数变成了o(m),但是在内核中还需要完成O(n)的主动遍历,所以还存在着下面的两个问题

  • select每次需要传值(10w个fds)

  • 内核主动遍历哪些fd可读可写

针对这个问题有没有办法解决呢,有是肯定有的,我们继续看下哈…

epoll多路复用

=========

如果内核中可以开辟一块空间,程序每收到一个连接就把这个连接的文件描述符存到这块内核中下次就不用重复传递了,减少了传递的过程,那么如何知道这些fd哪些是可读/可写的呢?曾经的方式是主动遍历,如果有1w个就会遍历1w次,如何优化这种主动遍历的方式让它变得更快一些;其实有一种事件驱动的方式,这个时候就需要epoll登场了。可以通过man epoll 命令看下epoll的文档

The epoll API performs a similar task to poll(2): monitoring multiple file descriptors to see if I/O is possible on any of them. The epoll API can be used either as an edge-triggered or a

level-triggered interface and scales well to large numbers of watched file descriptors. The following system calls are provided to create and manage an epoll instance:

  • epoll_create(2) creates a new epoll instance and returns a file descriptor referring to that instance. (The more recent epoll_create1(2) extends the functionality of epoll_create(2).)

  • Interest in particular file descriptors is then registered via epoll_ctl(2). The set of file descriptors currently registered on an epoll instance is sometimes called an epoll set.

  • epoll_wait(2) waits for I/O events, blocking the calling thread if no events are currently available.

可以看到epoll主要有三个命令epoll_create、epoll_ctl、epoll_wait

epoll_create

=============

epoll_create:内核会产生一个epoll 实例数据结构并返回一个文件描述符epfd,这个文件描述符其实描述的是上面介绍的内核中开辟的一块空间

DESCRIPTION

epoll_create() creates a new epoll(7) instance. Since Linux 2.6.8, the size argument is ignored, but must be greater than zero; see NOTES below.

epoll_create() returns a file descriptor referring to the new epoll instance. This file descriptor is used for all the subsequent calls to the epoll interface. When no longer required, the

file descriptor returned by epoll_create() should be closed by using close(2). When all file descriptors referring to an epoll instance have been closed, the kernel destroys the instance and

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

最后总结

搞定算法,面试字节再不怕,有需要文章中分享的这些二叉树、链表、字符串、栈和队列等等各大面试高频知识点及解析

最后再分享一份终极手撕架构的大礼包(学习笔记):分布式+微服务+开源框架+性能优化

image

图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频**

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-WhjA4Yki-1710761942641)]

最后总结

搞定算法,面试字节再不怕,有需要文章中分享的这些二叉树、链表、字符串、栈和队列等等各大面试高频知识点及解析

最后再分享一份终极手撕架构的大礼包(学习笔记):分布式+微服务+开源框架+性能优化

[外链图片转存中…(img-bl9RkwCR-1710761942641)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值