python 并发编程 多路复用IO模型详解

Python 并发编程 多路复用IO模型详解

一、什么是多路复用IO模型

在传统的 I/O 模型中,当一个线程或者进程要进行 I/O 操作的时候,会阻塞当前的任务,等待 I/O 完成后才能继续执行后续的任务。这种模式既浪费时间,也浪费资源,无法高效地利用 CPU。

多路复用 IO 模型是一种更加高效的 I/O 处理模型,在这种模式下,可以实现多个 I/O 任务的并发处理,避免了每个 I/O 任务的等待,提高了 CPU 的利用效率。常见的多路复用 IO 模型有 select、poll、epoll。

二、select 模块

1. 简介

select 是一种比较古老的多路复用 IO 模型,在 Python 中,它被封装在 select 模块下。select 的主要作用是监听文件描述符,当有一个文件描述符准备就绪以后,就会返回该文件描述符,从而实现多个 I/O 任务的并发处理。

2. select.select()

select.select() 方法的基本用法如下:

import select
import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(("127.0.0.1", 8888))
server_socket.listen()

inputs = [server_socket]

while True:
    readable, _, _ = select.select(inputs, [], [])
    for sock in readable:
        if sock == server_socket:
            client_socket, address = server_socket.accept()
            inputs.append(client_socket)
        else:
            data = sock.recv(1024)
            if not data:
                inputs.remove(sock)
                sock.close()
                continue
            msg = f"received message: {data.decode()}"
            sock.sendall(msg.encode())

在这个示例中,我们首先初始化一个 server_socket,将其加入 inputs 列表中。然后创建一个死循环,调用 select.select(inputs, [], []) 监听 inputs 中的文件描述符,如果有文件描述符准备就绪,就进入 for 循环进行处理。

如果准备就绪的文件描述符是 server_socket,那么我们就执行 server_socket.accept() 方法,创建一个新的套接字 client_socket,并将其加入 inputs 列表中。如果准备就绪的文件描述符是 client_socket,那么我们就执行 sock.recv(1024) 方法,接收客户端发送的数据,并返回一条响应消息。

3. select.poll()

select.poll() 方法的用法和 select.select() 类似,不同的是,select.poll() 更加高效,可以在象征性的文件描述符数量下应用于数以百万计的节点。

import select
import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(("127.0.0.1", 8888))
server_socket.listen()

poll = select.poll()
poll.register(server_socket, select.POLLIN)

fd_to_socket = {server_socket.fileno(): server_socket}

while True:
    events = poll.poll(10000)
    for fd, event in events:
        if fd == server_socket.fileno():
            client_socket, address = server_socket.accept()
            poll.register(client_socket, select.POLLIN)
            fd_to_socket[client_socket.fileno()] = client_socket
        elif event & select.POLLIN:
            data = fd_to_socket[fd].recv(1024)
            if not data:
                poll.unregister(fd)
                fd_to_socket[fd].close()
                del fd_to_socket[fd]
                continue
            msg = f"received message: {data.decode()}"
            fd_to_socket[fd].sendall(msg.encode())

在这个示例中,我们首先初始化一个 poll 对象,将 server_socket 注册到 poll 中。然后创建一个字典 fd_to_socket,将 server_socket 的文件描述符和对象存入该字典中。

接下来,我们进入死循环,调用 poll.poll(10000) 监听 poll 中的文件描述符,如果有文件描述符准备就绪,就进入 for 循环进行处理。

如果准备就绪的文件描述符是 server_socket,那么我们就执行 server_socket.accept() 方法,创建一个新的套接字 client_socket,并将其加入 poll 中,同时在 fd_to_socket 中创建该套接字的键值对。如果准备就绪的文件描述符是 client_socket,那么我们就执行 fd_to_socket[fd].recv(1024) 方法,接收客户端发送的数据,并返回一条响应消息。

三、epoll 模块

epoll 是一个比较新的多路复用 IO 模型,在 Python 中,它被封装在 epoll 模块下。epoll 是 Linux 2.6 内核提供的新的 IO 多路复用接口,旨在替代旧的 select/poll,相比旧的方式,epoll 能够有效地处理大量并发链接,性能更加优异。

import select
import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(("127.0.0.1", 8888))
server_socket.listen()

epoll = select.epoll()
epoll.register(server_socket.fileno(), select.EPOLLIN)

fd_to_socket = {server_socket.fileno(): server_socket}

while True:
    events = epoll.poll(1)
    for fd, event in events:
        if fd == server_socket.fileno():
            client_socket, address = server_socket.accept()
            epoll.register(client_socket.fileno(), select.EPOLLIN)
            fd_to_socket[client_socket.fileno()] = client_socket
        elif event & select.EPOLLIN:
            data = fd_to_socket[fd].recv(1024)
            if not data:
                epoll.unregister(fd)
                fd_to_socket[fd].close()
                del fd_to_socket[fd]
                continue
            msg = f"received message: {data.decode()}"
            fd_to_socket[fd].sendall(msg.encode())

在这个示例中,我们首先初始化一个 epoll 对象,将 server_socket 注册到 epoll 中。然后创建一个字典 fd_to_socket,将 server_socket 的文件描述符和对象存入该字典中。

接下来,我们进入死循环,调用 epoll.poll(1) 监听 epoll 中的文件描述符,如果有文件描述符准备就绪,就进入 for 循环进行处理。

如果准备就绪的文件描述符是 server_socket,那么我们就执行 server_socket.accept() 方法,创建一个新的套接字 client_socket,并将其加入 epoll 中,同时在 fd_to_socket 中创建该套接字的键值对。如果准备就绪的文件描述符是 client_socket,那么我们就执行 fd_to_socket[fd].recv(1024) 方法,接收客户端发送的数据,并返回一条响应消息。

四、总结

多路复用 IO 模型是实现高效多任务的关键之一,Python 提供了 select 和 epoll 模块,可以帮助我们实现非阻塞 IO 的应用程序。我们需要根据具体的应用场景和需求,选取合适的 I/O 模型。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:python 并发编程 多路复用IO模型详解 - Python技术站

(0)
上一篇 2023年5月16日
下一篇 2023年5月16日

相关文章

  • Java面试题冲刺第二十五天–并发编程2

    下面我将详细讲解“Java面试题冲刺第二十五天–并发编程2”的完整攻略。 标题 Java面试题冲刺第二十五天–并发编程2 内容 介绍 本次攻略主要是针对Java并发编程中的一些问题进行剖析和解决,主要涉及到以下几个方面: 线程池的使用 死锁的排查和解决 并发编程的常见问题和解决方法 线程池的使用 线程池是Java并发编程中非常重要的概念,通过线程池,我们…

    多线程 2023年5月17日
    00
  • Python多线程正确用法实例解析

    Python多线程正确用法实例解析 Python中的多线程可以提高程序的性能,但是在使用多线程时需要注意一些细节问题,避免出现错误。本篇文章将讲解Python多线程的正确用法,并给出两个示例来说明多线程的应用。 多线程简介 线程是程序执行的最小单元,多线程指的是程序同时执行多个线程来完成任务,可以提高程序执行效率。Python中的_thread模块和thre…

    多线程 2023年5月17日
    00
  • J2ee 高并发情况下监听器实例详解

    J2EE 高并发情况下监听器实例详解 什么是监听器 在J2EE中,监听器通常指的是实现了某个特定接口的Java类,用于在应用程序中监听某些特定的事件。当这些特定事件发生时,监听器类会被自动调用执行相关的处理逻辑。 因此,监听器可以在某个事件发生时,执行一些处理逻辑,以达到某种预期的目的。 监听器在高并发环境中的作用 在高并发应用场景下,监听器可以扮演各种重要…

    多线程 2023年5月16日
    00
  • Java并发编程中使用Executors类创建和管理线程的用法

    一、介绍 在Java并发编程中,线程池是一种重要的技术。通过线程池执行任务可以大大减少资源的开销,提高程序的性能,避免线程过多导致系统资源耗尽的情况。而Executors类就是Java提供的一个专门用于创建和管理线程池的工具类。 二、使用步骤 创建线程池 创建线程池的方式有多种,其中Executors类提供了丰富的静态方法来创建不同类型的线程池。比较常用的是…

    多线程 2023年5月16日
    00
  • PHP使用CURL_MULTI实现多线程采集的例子

    下面就详细讲解一下 “PHP使用CURL_MULTI实现多线程采集的例子”: 介绍 CURL是一个网络请求库,它可以以各种协议发送请求并获取响应。PHP内置了CURL扩展,使用它可以轻松地实现网络请求。CURL_MULTI是CURL的多线程版本,可以并发处理多个CURL请求。 在本篇文章中,我们将介绍如何利用PHP中的CURL_MULTI实现多线程采集。 步…

    多线程 2023年5月16日
    00
  • 服务器压力测试概念及方法(TPS/并发量)

    服务器压力测试概念及方法(TPS/并发量) 什么是服务器压力测试? 服务器压力测试是一种测试服务器在压力下的表现的方法。通过模拟大量用户访问、查询和交互,测试服务器在高负载情况下的性能,包括并发连接数、响应时间、事务吞吐量等指标。这些指标对于确定服务器的性能和确定是否需要升级或扩展服务器非常重要。 压力测试方法 1. TPS测试 TPS(Transactio…

    多线程 2023年5月16日
    00
  • HTML5之多线程(Web Worker)

    HTML5的一个重要特性是支持多线程(Web Worker),这使得在浏览器执行JavaScript代码时可以使用多个线程加快程序运行速度,提升用户体验。 前置知识 在介绍Web Worker之前,需要先了解下JavaScript中的单线程和异步编程。JavaScript运行在浏览器端时只有一个主线程,在这个主线程中执行各种操作,包括用户交互和执行代码等等,…

    多线程 2023年5月17日
    00
  • 高并发下如何避免重复数据产生技巧

    如何避免重复数据产生,在高并发环境下是一个非常重要的问题,因为一旦出现重复数据,就会影响整个系统的正常运行,甚至可能导致严重的数据安全问题。下面是一些可以避免重复数据产生的技巧: 数据库级别的锁定机制 在高并发环境下,一个经典的问题是“在同一时刻是否可以有多个用户同时修改同一条数据?” 事实上,这是不可能的,因为如果多个用户同时修改同一条数据,就会出现数据不…

    多线程 2023年5月17日
    00
合作推广
合作推广
分享本页
返回顶部