Python之进程+线程+协程(事件驱动模型、IO多路复用、select与epoll)


本篇文章是关于涉及网络编程与协程、进程之间结合的内容,其中事件驱动模型、IO多路复用、select与epoll的使用等方面的知识

一、事件驱动模型

1、事件驱动编程思想
是一种编程范式,跟面向对象、面向过程一样,就是一种编程思想。

2、事件驱动模型
通常,我们写服务器处理模型的程序时,有以下几种模型:

(1)每收到一个请求,创建一个新的进程,来处理该请求;
(2)每收到一个请求,创建一个新的线程,来处理该请求;
(3)每收到一个请求,放入一个事件列表,让主进程通过非阻塞I/O方式来处理请求

第(3)种就是协程、事件驱动的方式,第(3)种方式是大多数网络服务器采用的方式

目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件,这个事件就代表鼠标按下事件。事件驱动模型大体思路如下:

有一个事件(消息)队列;
鼠标按下时,往这个队列中增加一个点击事件(消息);
有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;
事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数

二、IO多路复用

就是可以同时监听多个连接;
比如socketserver,多个客户端连接,单线程下实现并发效果,就叫多路复用。
1、 阻塞 IO(blocking IO)
在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:

当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
所以,blocking IO的特点就是在IO执行的两个阶段都被block了

2、非阻塞 IO(non-blocking IO)

扫描二维码关注公众号,回复: 11238837 查看本文章

在网络IO时候,非阻塞IO也会进行recvform系统调用,检查数据是否准备好,与阻塞IO不一样,”非阻塞将大的整片时间的阻塞分成N多的小的阻塞,
所以进程不断地有机会 ‘被’ CPU光顾”。即每次recvform系统调用之间,cpu的权限还在进程手中,这段时间是可以做其他事情的,

也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。

3、IO 多路复用(IO multiplexing)
(1)它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。

当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。

分为select(任何系统下都通用的)、poll、epoll(效果最好);
另外, select的优势在于可以处理多个连接,不适用于单个连接

(2)用select实现并发聊天
服务端:

import socket
import select  #导入select模块

#实例化socket对象
sk=socket.socket()

sk.bind(("192.168.43.247",8801))
sk.listen(5)
inputs=[sk,]

#收发消息
while True:
    #开启select监听,将获得的链接存在r里面
    r,w,e=select.select(inputs,[],[],5)

    for obj in r:   #此时r内就只有[sk,]
        if obj==sk:
            conn,add=obj.accept()
            print(conn)
            inputs.append(conn)
        else:
            data_byte=obj.recv(1024)
            print(str(data_byte,'utf8'))
            inp=input('回答%s号客户>>>'%inputs.index(obj))
            obj.sendall(bytes(inp,'utf8'))

    print('>>>',r)

客户端:

import socket

sk = socket.socket()
sk.connect(('192.168.43.247',8801))

while True:
    inp = input("请输入聊天信息:")
    sk.sendall(bytes(inp, "utf8"))
    data = sk.recv(1024)
    print(str(data, 'utf8'))

执行结果:
IO多路复用

猜你喜欢

转载自blog.csdn.net/Viewinfinitely/article/details/105607451