码迷,mamicode.com
首页 > 其他好文 > 详细

IO阻塞模型

时间:2018-02-10 16:59:25      阅读:167      评论:0      收藏:0      [点我收藏+]

标签:删除   复用   hat   用户   其他   列表   解决   线程   目的   

阻塞IO

技术分享图片

  • 当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。

  • 而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存, 然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
  • 所谓阻塞型接口是指系统调用(一般是IO接口)不返回调用结果并让当前线程一直阻塞
    只有当该系统调用获得结果或者超时出错时才返回。

  • 实际上,除非特别指定,几乎所有的IO接口 ( 包括socket接口 ) 都是阻塞型的。这给网络编程带来了一个很大的问题,如在调用recv(1024)的同时,线程将被阻塞,在此期间,线程将无法执行任何运算或响应任何的网络请求。
    • 简单的解决办法
      • 在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。
    • 该方案的问题是
      • 开启多进程或都线程的方式,在遇到要同时响应成百上千路的连接请求,则无论多线程还是多进程都会严重占据系统资源,降低系统对外界响应效率,而且线程与进程本身也更容易进入假死状态.

非阻塞IO

技术分享图片

  • 非阻塞的recvform系统调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error
  • 进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。
  • 轮询检查操作系统内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。
  • 需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。但是由于是本地操作,所以速度很快,根本察觉不到;
#服务端
from socket import *

server = socket(AF_INET, SOCK_STREAM)
server.bind((‘127.0.0.1‘,8099))
server.listen(5)
server.setblocking(False)

rlist=[]
wlist=[]
while True:
    try:
        conn, addr = server.accept()
        rlist.append(conn)
        print(rlist)

    except BlockingIOError:
        del_rlist=[]
        for sock in rlist:
            try:
                data=sock.recv(1024)
                if not data:
                    del_rlist.append(sock)
                wlist.append((sock,data.upper()))
            except BlockingIOError:
                continue
            except Exception:
                sock.close()
                del_rlist.append(sock)

        del_wlist=[]
        for item in wlist:
            try:
                sock = item[0]
                data = item[1]
                sock.send(data)
                del_wlist.append(item)
            except BlockingIOError:
                pass

        for item in del_wlist:
            wlist.remove(item)


        for sock in del_rlist:
            rlist.remove(sock)

server.close()


#客户端
from socket import *
c=socket(AF_INET,SOCK_STREAM)
c.connect((‘127.0.0.1‘,8080))

while True:
    msg=input(‘>>: ‘)
    if not msg:continue
    c.send(msg.encode(‘utf-8‘))
    data=c.recv(1024)
    print(data.decode(‘utf-8‘))

I0模型之多路复用

技术分享图片

  • 当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
  • 这个图和blocking IO的图其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用(select和recvfrom),而blocking IO只调用了一个系统调用(recvfrom)。但是,用select的优势在于它可以同时处理多个connection。
  • 在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
# 服务端,可以实现同时服务多个客户端
from socket import *
import select

server = socket(AF_INET, SOCK_STREAM)
server.bind((‘127.0.0.1‘, 8800))
server.listen(5)
server.setblocking(False)
print(‘等待连接.....‘)

r_list = [server, ]
w_list = []
w_data = {}

while True:
    rl, wl, el = select.select(r_list, w_list, [], 0.5)
    # 调用select方法,开始监听socket, 主要是accept连接状态, conn的接收和发送数据
    print(rl)
    print(wl)

    # 处理rl列表,等待和接收数据
    for sock in rl:
        if sock == server:
            # 处理accept阻塞, 更新rl列表
            conn, addr = sock.accept()
            r_list.append(conn)  # 将读取数据就绪状态的conn放入rl列表中
        else:
            # 处理recv阻塞, 更新wl列表,rl列表和wlist字典
            try:
                data = sock.recv(1024)  # 当前conn接收到数据
                if not data:
                    # 客户端连接断开, 首先关闭服务端的管道
                    sock.close()
                    # 从接收数据列表中删除conn对象
                    r_list.remove(sock)
                    continue
                w_list.append(sock)  # 将发送数据就绪的conn放入wl列表中
                w_data[sock] = data.upper()

            except Exception:
                sock.close()
                r_list.remove(sock)

    # 处理wl列表,发送数据,更新wl列表和wdata字典
    for sock in wl:
        sock.send(w_data[sock])
        w_list.remove(sock)
        w_data.pop(sock)
# 客户端
from socket import *

client = socket(AF_INET, SOCK_STREAM)
client.connect((‘127.0.0.1‘, 8800))
while True:
    msg = input(‘>>> ‘).strip()
    if not msg: continue
    client.send(msg.encode(‘utf-8‘))

    data = client.recv(1024)
    print(data.decode(‘utf-8‘))

IO模型比较分析

  • blocking vs non-blocking

    调用blocking IO会一直block住对应的进程直到操作完成,而non-blocking IO在kernel还准备数据的情况下会立刻返回。

  • synchronous IO和asynchronous IO的区别

    A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes
    An asynchronous I/O operation does not cause the requesting process to be blocked

IO阻塞模型

标签:删除   复用   hat   用户   其他   列表   解决   线程   目的   

原文地址:https://www.cnblogs.com/fqh202/p/8438872.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!