Python并发机制(三)——异步I/O(select & poll)

本文深入探讨Python的异步机制,主要介绍I/O多路复用中的select和poll方法。select系统调用用于监视多个文件描述符,当有文件描述符准备就绪时,内核会修改其标志位。尽管select具有良好跨平台性,但存在单进程监视fd数量限制的问题。poll则提供了一种类似的功能,通过eventmask进行操作,支持更多事件类型,如读取、写入等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

python异步机制(三)——I/O多路复用

select

select通过一个select()系统调用来监视多个文件描述符组成的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,select本质上是通过设置或检查存放fd标志位的数据结构来进行下一步处理

优点:

  • 具有良好的跨平台性

缺点:

  • 单个进程可监视的fd数量被限制(在Linux上一般为1024)
  • 需要维护一个用来存放大量fd的数据结构(数组:线性表),使得用户空间和消耗大

服务器端:

#!/usr/bin/python
#coding=utf-8

import socket,sys,select,Queue

host = (sys.argv[1], int(sys.argv[2]))

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

#阻塞与端口复用
sock.setblocking(False)
#SOL_SOCKET(套接字描述符),SO_REUSEADDR(端口复用,让端口释放后可以被立即使用)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(host)
sock.listen(5)

rlist = [sock]
wlist = []
msg_que = {}

timeout = 60

while True:
  rs, ws ,es = select.select(rlist, wlist, rlist, timeout)

  if not (rs or ws or es):
      print "timeout..."
      continue

  for r in rs:
      if r is sock:
          conn,addr = r.accept()
          print "connected by %s"%addr[1]
          conn.setblocking(False)
          rlist.append(conn)
          msg_que[conn] = Queue.Queue()
      else:
          data = r.recv(1024)
          if data:
              print data
              msg_que[r].put(data)
              if r not in wlist:
                  wlist.append(r)
          else:
              if r in wlist:
                  wlist.remove(r)
              rlist.remove(r)
              r.close
              del msg_que[r]

  for w in ws:
      try:
        #等同于get(0)
        msg = msg_que[w].get_nowait()
      except Queue.Empty:
        print 'msg empty'
        wlist.remove(w)
      else:
        w.send(msg)

  for e in es:
      print 'except:',s.getpeername()
      if e in rlist:
          rlist.remove(e)
      if e in wlist:
          wlist.remove(e)
      s.close
      del msg_que[e]

客户端:

#!/usr/bin/python
#coding=utf-8

import socket,sys,time

host = (sys.argv[1],int(sys.argv[2]))

sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect(host)

data = 1
while data:
    data = raw_input('please input:')
    sock.send(data)

运行结果:

linux@linux:~$ python test18.py "" 8880
connected by 44980
fds
connected by 44982
afds
afds

poll

poll与select类似,其注册时需要带有一个eventmask,eventmask是一些按位或标记.

POLLIN: 用于读取数据
POLLPRI: 用于读取紧急数据
POLLOUT: 准备写入
POLLERR: 错误情况
POLLHUP: 保持状态
POLLNVAL: 无效请求

下面是一个多路文件复制例子:

#! /usr/bin/python
# -*- coding: utf-8 -*-

import select
blksize = 8192

def read_write(fromfd, tofd):

    readbuf = fromfd.read(blksize)
    if readbuf:
        tofd.write(readbuf)
        tofd.flush()

    return len(readbuf)

def copy_poll(fromfd1, tofd1, fromfd2, tofd2):

    # 定义只读事件
    READ_ONLY = (select.POLLIN |
        select.POLLPRI |
        select.POLLHUP |
        select.POLLERR
        )

    totalbytes = 0

    if not (fromfd1 or fromfd2 or tofd1 or tofd2):
        return 0
    fd_dict = {fromfd1.fileno():fromfd1, fromfd2.fileno():fromfd2}

    p = select.poll()

    # 注册文件描述符
    p.register(fromfd1, READ_ONLY)
    p.register(fromfd2, READ_ONLY)

    while True:
        # 轮询已注册事件是否准备好  
        result = p.poll()
        if len(result) != 0:
            for fd, events in result:
                if fd_dict[fd] is fromfd1:
                    if events & (select.POLLIN | select.POLLPRI):
                        bytesread = read_write(fromfd1, tofd1)
                        totalbytes += bytesread

                    elif events & (select.POLLERR):
                        p.unregister(fd_dict[fd])

                if fd_dict[fd] is fromfd2:
                    if events & (select.POLLIN | select.POLLPRI):
                        bytesread = read_write(fromfd2, tofd2)
                        totalbytes += bytesread

                    elif events & (select.POLLERR):
                        p.unregister(fd_dict[fd])


        if bytesread <= 0:
            break

    return totalbytes

def main():

    fromfd1 = open('/home/linux/nginx-1.11.7.tar.gz', 'r')
    fromfd2 = open('/home/linux/wow.tar.gz', 'r')

    tofd1 = open('/home/linux/blogtest1', 'a')
    tofd2 = open('/home/linux/blogtest2', 'a')

    totalbytes = copy_poll(fromfd1, tofd1, fromfd2, tofd2)
    print "already copy %d" % totalbytes
    return 0

if __name__ == '__main__':

    main()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值