IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 从解决问题的思路去理解IO复用的由来 -> 正文阅读

[网络协议]从解决问题的思路去理解IO复用的由来

写在前面

近日一直在学习理解IO多路复用的相关概念。协程,epoll,阻塞非阻塞等等概念看了很多资料,博客,视频,感觉总是差点意思,直到刚才看了这篇文章才有一点融会贯通的感觉。很多底层概念是相互依赖的,只说其一就会分裂,只说用法不说原因就会莫名其妙。我一向喜欢从提出问题,解决问题的思路来去理解概念。下面讲讲我的一些个人理解

附上大神的文章

一些浅显的知识(或者说常识

  1. 线程是CPU调度的最小执行单位。CPU给每个线程分配时间片。当线程时间片用完,或者由于某些原因阻塞(如IO阻塞)时,操作系统会进行调度,切换线程的上下文。虽然我们说线程切换的代价相对于进程切换来说小了很多,但是仍然是不可忽略的系统开销。
  2. 对于进程与线程之间的关系,并不建议用树的结构去理解,也就是 一个进程里面有包含多个线程。虽然逻辑是这样的,但是我更推荐从物理上去理解:所有的线程有一张表,所有的进程有一张表,二者互不干涉。只不过每一个线程都会标识属于哪一个进程,这样不同线程间就能共享一个进程的地址空间,资源。引自这个视频
    在这里插入图片描述
    这里做一个我理解的图。进程其实相当于一个家,一个存放资源的base,不同的线程通过base去访问资源。这里花篇幅陈述这个概念,是希望大家明确:线程是调度的基本单位,建立起这个清晰的概念。

下面回归正题:

阻塞式IO

listenfd = socket();   // 打开一个网络通信端口
bind(listenfd);        // 绑定
listen(listenfd);      // 监听
while(1) {
  connfd = accept(listenfd);  // 阻塞建立连接
  int n = read(connfd, buf);  // 阻塞读数据
  doSomeThing(buf);  // 利用读到的数据做些什么
  close(connfd);     // 关闭连接,循环等待下一个连接
}

这是服务端处理客户端请求的代码

显然,accept函数,read函数都可能会发生阻塞。
在accept这里发生阻塞很正常,但是read发生阻塞,会有问题:
read被阻塞,我这个线程就阻塞挂起了,切换到其他线程,那就意味着它无法再去accept其他客户端的请求。(因为当前是单线程或者说单进程来处理请求)。

在这里插入图片描述
为了解决:**“它无法再去accept其他客户端的请求”**这个问题,我们这样修改代码

while(1) {
  connfd = accept(listenfd);  // 阻塞建立连接
  pthread_create(doWork);  // 创建一个新的线程
}
void doWork() {
  int n = read(connfd, buf);  // 阻塞读数据
  doSomeThing(buf);  // 利用读到的数据做些什么
  close(connfd);     // 关闭连接,循环等待下一个连接
}

这样每一个连接请求用一个线程来处理:主线程接收到连接请求就建立一个子线程来处理它,然后继续监听请求。这样就避免了read函数阻塞带来的问题。

**但是带来新的问题:**比方说现在有线程A,分配到了CPU的时间片,然后他去执行doWork函数,走到read函数,发现客户端没给发数据,所以只能阻塞,阻塞就要挂起,就要由用户态切换到内核态,就要进行线程上下文切换,就有开销
试想,如果n个线程都没有收到信息,那cpu的就在它们之间反复横跳,啥也没干,净切换线程上下文。

所以read函数阻塞的问题一定要解决

非阻塞式IO

操作系统为我们提供非阻塞的read系统调用,如果当前没有可读的内容,read函数就返回-1,然后线程去干其他事情,而不是导致整个线程阻塞挂起。
线程会采取轮询的方式,检查read是否有返回值。
当read返回值不为1时,说明数据已经从网卡拷贝到了内核的缓冲区,则开始将数据从内核的缓冲区load到用户的数据区。

在这里插入图片描述
到这里似乎已经很完美。 **但是其实还是有问题:**线程轮询使用read系统调用,还是会导致用户态与内核态的频繁切换。

刚才,操作系统是在不停地切换线程,切换用户态与内核态;现在没有频繁切换线程,但是仍然在频繁地切换内核态与用户态。

比方说,现在某个线程分配到了CPU的时间片,美滋滋去doWork,使用一次read系统调用,发现返回为-1,然后就去干其他的,每隔一会儿轮询使用一次read,每次都会从用户态切换到内核态,检查fd,再切回去。好好一个时间片啥也没干,全用在系统调用,切换状态上了。

如果IO等待时间不长,倒问题不大;如果等待时间长,CPU就处于空耗状态。
此外,一个线程对应一个连接,无法适应高并发的问题。毕竟线程需要占用资源,而系统资源是有限的。

IO多路复用

相比于**“一线程,一连接”的方式,我们能不能“一线程,多连接”**呢?(这里可以引入协程的概念)
这正是IO多路复用的核心思想。我们用一个线程监听多个连接的fd(文件描述符),再对每一个fd调用非阻塞的read。这样就避免了线程过多的问题。

你会想问,那非阻塞轮询导致用户态与内核态频繁切换的问题还是没解决啊?
别急,select,poll,epoll最终会给出答案。

这三个方法的核心都是,将轮询导致无意义的check,转交给内核来完成(用户态轮询->内核态轮询),这样只需要一次内核-用户态切换就行了。

源码级讲解看这篇文章

  • select方案

我们用一个线程A,不断地监听客户端的请求,并将生成的fd添加到一个集合当中。

我们用另外一个线程B,调用select函数,将bitmap传入到内核,由内核轮询check各个fd的状态,如果fd可读,则将相应的位图置为1,否则为0. 此时B是阻塞状态的。
当select函数返回后,B重新获得CPU,根据位图,B能知道哪些fd已经可以读入数据,虽然仍需要遍历整个集合,但是不再需要对没有准备好的fd去调用read,避免了无意义的系统调用。
在这里插入图片描述
可以看出,总开销是一次select系统调用+若干次read系统调用

  • poll方案

poll方案的进步在于声明了一个数据结构来表示fd,打破了select只能监听1024个fd的限制(因为这个数据结构的数组,想开多大就开多大)

  • epoll方案

epoll方案将文件描述符的集合变成了内核态与用户态共享。这样省去了select调用时由用户态切换为内核态的开销。

此外,epoll会告诉用户有多少个fd是已经准备好的,并将它们放到列表的最前面(底层用红黑树来组织),这样epoll返回后,就不再需要遍历整个集合,而只需要遍历准备好的n个,并调用read即可。

内核也不再通过轮询的方式找到就绪的文件描述符,而是谁准备好了就通知一声,内核就把它的fd放到列表前面,也就是异步的方式。

到这里似乎所有问题都得到了完美的解决,其实不然。
虽然epoll函数是非阻塞的,也就是判断数据是否已经从网卡拷贝到内核缓冲区是非阻塞的,但是,实际读取,将数据从内核拷贝到用户数据区,这个过程依然是阻塞的!

只要阻塞,就会涉及到线程切换。

这里引入协程的概念。协程本质是一些函数,能够在用户态进行切换,所以代价比线程切换要小得多。因此协程也叫做用户态线程。这里我们将read函数做一步封装,封装成一个函数,也就是一个协程。如果当前协程A的read被阻塞了,就会切换到另一个协程B去read,当A read完之后,自动切换回A,恢复协程的上下文。

这样,就解决了阻塞的问题。

感觉看别人的博客,视频终归很难形成自己的理解。还是要多多结合源码

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-04-06 16:25:55  更:2022-04-06 16:27:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 4:44:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码