Colingo碎碎念

Programming language, 架构, 分布式, 微服务, iOS, Android

0%

io-multiplexing(多路复用)

nginx, redis,虽然都是单线程,支持高并发的设置典范。但是同时,如何理解,单线程为什么能够支撑很高的并发及吞吐量。原因有很多,比如redis主要是使用内存,没有IO的限制。今天主要是记录一下,另一个重要的原因,就是IO的多路复用。

什么是多路复用

“I/O多路复用”这个坑爹翻译可能是这个概念在中文里面如此难理解的原因。所谓的I/O多路复用在英文中其实叫 I/O multiplexing. 如果你搜索multiplexing啥意思,基本上都会出这个图:

I/O multiplexing

多路复用通常表示在一个信道传输多路信号

I/O multiplexing中的multiplexing是指在单个线程通过记录跟踪一个I/O流的状态来同时管理多个I/O流。发名它的的原因,是尽量多的提高服务器的吞吐能力。看一下图能直观了解究竟是怎么回事:

在同一个线程里面, 通过拨开关的方式,来同时传输多个I/O流。
nginx使用epoll接收请求的过程是怎样的”, 多看看这个图就了解了。ngnix会有很多链接进来, epoll会把他们都监视起来,然后像拨开关一样,谁有数据就拨向谁,然后调用相应的代码处理。

linux IO多路复用有epoll, poll, select,epoll性能比其他几都要好。 那为什么还会有三个呢?之所以有这三个存在,其实是他们出现是有先后顺序的。

背景

I/O多路复用这个概念被提出来以后, select是第一个实现 (1983 左右在BSD里面实现的)。

一 、select 被实现以后,很快就暴露出了很多问题。

  • select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的。
  • select 如果任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但是并不会告诉你是那个sock上有数据,于是你只能自己一个一个的找,10几个sock可能还好,要是几万的sock每次都找一遍,这个无谓的开销就颇有海天盛筵的豪气了。
  • select 只能监视1024个链接, 这个跟草榴没啥关系哦,linux 定义在头文件中的,参见FD_SETSIZE。
  • select 不是线程安全的,如果你把一个sock加入到select, 然后突然另外一个线程发现,尼玛,这个sock不用,要收回。对不起,这个select 不支持的,如果你丧心病狂的竟然关掉这个sock, select的标准行为是。。呃。。不可预测的, 这个可是写在文档中的哦.

二、于是14年以后(1997年)一帮人又实现了poll, poll 修复了select的很多问题

  • poll 去掉了1024个链接的限制,于是要多少链接呢, 主人你开心就好。
  • poll 从设计上来说,不再修改传入数组,不过这个要看你的平台了,所以行走江湖,还是小心为妙。

其实拖14年那么久也不是效率问题, 而是那个时代的硬件实在太弱,一台服务器处理1千多个链接简直就是神一样的存在了,select很长段时间已经满足需求。

但是poll仍然不是线程安全的, 这就意味着,不管服务器有多强悍,你也只能在一个线程里面处理一组I/O流。你当然可以那多进程来配合了,不过然后你就有了多进程的各种问题。

于是5年以后, 在2002, 大神 Davide Libenzi 实现了epoll.

三、epoll 可以说是I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题

  • epoll 现在是线程安全的。
  • epoll 现在不仅告诉你socket组里面数据,还会告诉你具体哪个socket有数据,你不用自己去找了。

最后举个例子来说明

一个饭店服务员(一个线程),店里有10几桌客人等点餐(先看menu),突然有一桌客户叫“服务员点菜”(事件),你小跑过去给记录一下点的东西,然后就让客人等上菜,突然又一桌要点菜,你又过去下单,就这样一个服务员服务好多桌,有时可能其他桌都在讨论点什么好并不点单,这时服务员处于空闲状态,可以干点别的玩玩手机。至于epoll与select,poll的区别在于后两者的场景中客人不喊服务器,你要挨个问要不要点菜,没时间玩手机了。io多路复用大概就是指这多桌客人(一般餐厅一片区域)共用一个服务员。

原文链接