python IO多路复用理解记录

一、 简述:

1什么是多路 I/O 复用机制

  • Python 中的多路 I/O 复用机制是一种高效的编程技巧,用于同时监视多个文件描述符是否有可读、可写或错误事件,并在这些描述符中任意一个或多个有事件发生时立即进行响应。

2 使用多路I/O 复用机制的目的

  • 同时处理多个连接时提高网络I/O效率,从而提高程序的整体性能。

 3 适用场景

  • 各种 I/O 密集型应用程序的编程。

二、常用机制

Python,常用多路I/O复用机制可供选择,包括selectpollepollasyncio等。这些机制都提供了一种方式来同时监视多个I/O事件,并且可以有效地处理并发的I/O操作。

  • selectselect是最基本和最常见的多路I/O复用机制之一,适用于大多数Unix平台。它通过调用select.select()函数来等待一组文件描述符上的事件,并返回已就绪的文件描述符列表。select的一个缺点是它对于大量的文件描述符会有性能问题。

  • pollpoll是类似于select的多路I/O复用机制,但在处理大量文件描述符时性能更好。它通过调用select.poll()创建一个poll对象,并使用register()方法注册文件描述符及其感兴趣的事件。然后,使用poll()方法等待事件的发生,并返回就绪的文件描述符列表。

  • epollepoll是在Linux上使用的高性能多路I/O复用机制。它使用内核事件通知机制来实现高效的I/O事件监视,适用于大规模的并发连接。epoll提供了select.epoll()函数来创建一个epoll对象,并使用register()方法注册文件描述符及其感兴趣的事件。然后,使用poll()方法等待事件的发生,并返回就绪的文件描述符列表。

  • asyncioasyncio是Python标准库中提供的异步I/O框架。它基于协程和事件循环的概念,提供了高层次的抽象来处理异步I/O操作。使用asyncio,可以通过asyncio.select()函数等待多个异步I/O操作,并在事件就绪时执行相应的回调函数。asyncio对于构建高性能的异步网络应用非常有用。

二、示列:

#1 select示例:
import select
import socket

# 创建套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('localhost', 8080))
server_socket.listen(5)
inputs = [server_socket]

while True:
    """
    参数说明:
    rlist(Readable List):包含需要监视可读事件的文件描述符的列表。当列表中的文件描述符准备好从其读取数据时,select 函数将返回这些文件描述符。
    wlist(Writable List):包含需要监视可写事件的文件描述符的列表。当列表中的文件描述符准备好向其写入数据时,select 函数将返回这些文件描述符。
    xlist(Exceptional List):包含需要监视异常事件的文件描述符的列表。当列表中的文件描述符发生异常情况时,如连接错误或带外数据,select 函数将返回这些文件描述符。
    """
readable, writable, exceptional = select.select(inputs, [], []) for sock in readable: if sock is server_socket: # 新的连接 client_socket, client_address = server_socket.accept() inputs.append(client_socket) else: # 可读数据 data = sock.recv(1024) if data: # 处理数据 print(data.decode()) else: # 客户端关闭连接 inputs.remove(sock) sock.close()
# 2poll示例: import select import socket # 创建套接字 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(('localhost', 8080)) server_socket.listen(5) poller = select.poll() poller.register(server_socket, select.POLLIN) while True: events = poller.poll() for fd, event in events: if fd == server_socket.fileno(): # 新的连接 client_socket, client_address = server_socket.accept() # 参数为可迭代的对象 poller.register(client_socket, select.POLLIN) else: # 可读数据 sock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM) data = sock.recv(1024) if data: # 处理数据 print(data.decode()) else: # 客户端关闭连接 poller.unregister(fd) sock.close()

#3 epoll示例: import select import socket # 创建套接字 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(('localhost', 8080)) server_socket.listen(5) epoller = select.epoll() epoller.register(server_socket.fileno(), select.EPOLLIN) while True: events = epoller.poll() for fd, event in events: if fd == server_socket.fileno(): # 新的连接 client_socket, client_address = server_socket.accept() client_socket.setblocking(0) ''' 常用的events标志有: EPOLLIN:表示可读事件 EPOLLOUT:表示可写事件 EPOLLRDHUP:表示对端连接关闭 EPOLLERR:表示错误事件 EPOLLHUP:表示挂起事件 ''' epoller.register(client_socket.fileno(), select.EPOLLIN) else: # 可读数据 sock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM) data = sock.recv(1024) if data: # 处理数据 print(data.decode()) else: # 客户端关闭连接 epoller.unregister(fd) sock.close()

#4 asyncio示例: import asyncio async def handle_client(reader, writer): data = await reader.read(1024) message = data.decode() addr = writer.get_extra_info('peername') print(f"Received {message} from {addr}") # 响应客户端 response = "Hello, client!" writer.write(response.encode()) await writer.drain() writer.close() async def main(): server = await asyncio.start_server( handle_client, 'localhost', 8080) addr = server.sockets[0].getsockname() print(f'Serving on {addr}') async with server: await server.serve_forever() asyncio.run(main())

三、区别 

  主要对select、poll、epoll 进行比较

  1. 适用系统不同:
  • select、poll:在多个操作系统上均可使用;
  • epoll:仅能在 Linux 系统上使用。
  1. 监听数量不同:
  • select:最多支持 1024 个文件描述符的监听;
  • poll:支持数目比 select 多,但仍有限制;
  • epoll:支持的数量没有明确的上限。
  1. 内部实现不同:
  • select、poll 通过遍历监听的所有文件描述符来检测是否有 I/O 事件发生,效率较低;
  • epoll 利用 epoll_ctl 系统调用来维护和更新事件列表,通过 epoll_wait 来通知进程哪些文件有读写操作的事件发生,效率更高。
  1. 事件注册方式不同:select、poll 的FD集合是一个静态的数据结构,因此每次注册事件都需要构建一个新的FD集合,效率较低;
  • epoll 采用基于事件的就绪通知方式,采用 epoll_ctl() 函数向内核注册感兴趣的事件类别。

         5.asyncio与其他三种的区别:

  • 是Python标准库提供的异步I/O框架。
  • 基于协程和事件循环的概念,提供了高层次的抽象来处理异步I/O操作。
  • 使用asyncio.select()函数等待多个异步I/O操作,并在事件就绪时执行相应的回调函数。
  • 强调基于事件驱动的编程模型,可以方便地编写异步代码。
 这些多路I/O复用机制在实现方式、可移植性和性能方面有所差异。选择适当的机制取决于应用程序的需求、目标平台和预期的并发规模。
posted @ 2023-05-06 14:37  行走的ID  阅读(169)  评论(0编辑  收藏  举报