Python技术迷

select、poll 和 epoll 模型的区别。

昨天晚上加班到快十点多,我赶紧买了点,吃到一半手机响了,我们组那个小李突然问我,"哥,select和poll和epoll到底有啥区别啊?" 我还在嗦粉,就一边嘬着一边给他整了点,顺手写在这儿,省得下回再有人问我就直接甩链接了。

其实吧,这仨玩意你要真想说起来,感觉十年前就已经被大家吐槽烂了,但是面试老爱考,而且写网络服务端的人绕不开。你比如我最早搞socket那会,select都用烂了,等后来稍微上点强度,那种十几万连接那种,epoll才算救命稻草。

先说select,真是上古遗物

select这东西,八九十年代就有了。你要用Python玩的话,最简单直接:

import select
import socket

sock = socket.socket()
sock.bind(('127.0.0.1', 8888))
sock.listen(5)

inputs = [sock]
whileTrue:
    readable, _, _ = select.select(inputs, [], [])
for s in readable:
if s is sock:
            conn, addr = sock.accept()
            inputs.append(conn)
else:
            data = s.recv(1024)
if data:
                print(data)
else:
                inputs.remove(s)
                s.close()

就像上面这段,反正每次你都得把所有fd(文件描述符)塞到一个list里面,然后select帮你监控这些fd是不是有事发生。缺点嘛,最恶心的其实有俩:

  1. 支持的fd数量有限制,1024个(有的系统能改,懒得折腾),多了不行。
  2. 每次调用都得把整个list遍历一遍,成百上千的连接,遍历一次就很慢,而且select还得把所有fd都拷贝一遍,这一步开销不小。

poll出来救场了,但也就那样

后来有了poll,和select比,没啥本质飞跃——主要是支持的fd数变多了,理论上只受内存影响,不用那个死板的1024限制了,但是底层其实也是遍历一遍,fd数量一大CPU直接原地去世。

poll的Python用法,其实很像:

import select
import socket

sock = socket.socket()
sock.bind(('127.0.0.1', 8888))
sock.listen(5)

poller = select.poll()
poller.register(sock.fileno(), select.POLLIN)
fd_map = {sock.fileno(): sock}

whileTrue:
    events = poller.poll()
for fd, event in events:
        s = fd_map[fd]
if s is sock:
            conn, addr = sock.accept()
            poller.register(conn.fileno(), select.POLLIN)
            fd_map[conn.fileno()] = conn
else:
            data = s.recv(1024)
if data:
                print(data)
else:
                poller.unregister(fd)
                fd_map.pop(fd)
                s.close()

epoll:来了,终于能撑住高并发了

你只要做过高并发的长连接服务,基本都知道,Linux后来出了epoll,直接就是为了解决前面那堆select/poll的坑的。epoll支持**"事件驱动"和"内核事件通知"**,有点像操作系统直接帮你管理"哪个fd有事了",你再去处理。

Python里用epoll其实跟poll差不多,就是接口不一样:

import select
import socket

sock = socket.socket()
sock.bind(('127.0.0.1', 8888))
sock.listen(5)

epoller = select.epoll()
epoller.register(sock.fileno(), select.EPOLLIN)
fd_map = {sock.fileno(): sock}

whileTrue:
    events = epoller.poll()
for fd, event in events:
        s = fd_map[fd]
if s is sock:
            conn, addr = sock.accept()
            epoller.register(conn.fileno(), select.EPOLLIN)
            fd_map[conn.fileno()] = conn
else:
            data = s.recv(1024)
if data:
                print(data)
else:
                epoller.unregister(fd)
                fd_map.pop(fd)
                s.close()

epoll最大优点是——连接数成千上万也不卡了,因为内核帮你维护了"活跃"的fd集合,你只关心有事件的那几个,CPU也省心多了。

总结吧,不整啥花活

  • select老掉牙了,低并发还能用,连接多了卡爆。
  • poll勉强能上大点量,但也就那样,遍历一大堆,还是慢。
  • epoll适合Linux高并发场景,写服务端、网关啥的都靠它。

顺便吐槽一句,Windows下没epoll,搞多路复用的都挺费劲...你要是python做高并发服务器,建议上Linux,再不行就直接asyncio一把梭吧。

-END-

我为大家打造了一份RPA教程,完全免费:https://www.songshuhezi.com/rpa.html

🔥虎哥私藏精品🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领