摘要:简单来说就是一个操作系统提供的回调机制。其中这一步是创建,是做一个调用,后面的是轮询,这一步是根据返回的查找对应的回调函数回调。这样状态从多个线程的多个栈上,变成了只有一个线程,但是在线程内部有一个来维护单线程内多个并发流程的状态。
为了让I/O阻塞的时候,程序还可以去干别的。除了使用线程模型,让操作系统的内核去调度多个线程,Windows提供了IOCP机制。简单来说就是一个操作系统提供的回调机制。分成四个步骤
生成key,并建立映射关系:向操作系统创建一个key,程序内部把这个key和一个回调函数对应起来
调用:执行阻塞的I/O操作,并指定key来对应这个I/O操作
轮询,返回key:程序轮询操作系统询问是否有新的I/O操作完成,如果有完成的会返回对应的key
用key查找,并回调:因为创建key的时候内部已经和一个回调函数对应起来了,所以这个时候之前映射好的函数会被回调
前面的例子太复杂了,我们把accept后面的操作全部忽略掉。多带带看一个服务器接收客户端连接的代码:
import socket from asyncio import _overlapped import struct listen_sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=socket.IPPROTO_IP) listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) listen_sock.bind(("0.0.0.0", 9090)) listen_sock.listen(0) NULL = 0 concurrency=0xffffffff _iocp = _overlapped.CreateIoCompletionPort(_overlapped.INVALID_HANDLE_VALUE, NULL, 0, concurrency) _overlapped.CreateIoCompletionPort(listen_sock.fileno(), _iocp, 0, 0) conn_sock = socket.socket(listen_sock.family) conn_sock.settimeout(0) ov = _overlapped.Overlapped(NULL) ov.AcceptEx(listen_sock.fileno(), conn_sock.fileno()) def on_accepted(): buf = struct.pack("@P", listen_sock.fileno()) conn_sock.setsockopt(socket.SOL_SOCKET, _overlapped.SO_UPDATE_ACCEPT_CONTEXT, buf) conn_sock.settimeout(listen_sock.gettimeout()) print("connected from %s:%s" % conn_sock.getpeername()) return conn_sock, conn_sock.getpeername() callback_map = {} if ov.pending: callback_map[ov.address] = on_accepted else: on_accepted() while True: # wait maximum 1 second status = _overlapped.GetQueuedCompletionStatus(_iocp, 1000) if status is None: continue # try again err, transferred, key, address = status callback = callback_map[address] callback() break
这段代码使用了Python 3.4。其中 _overlapped.Overlapped(NULL) 这一步是创建key,ov.AcceptEx(listen_sock.fileno(), conn_sock.fileno()) 是做一个I/O调用,后面的 _overlapped.GetQueuedCompletionStatus(_iocp, 1000) 是轮询,callback_map[address] 这一步是根据返回的key查找对应的回调函数回调。
这种实现方式与前面基于线程的方式显著不同:
程序内状态的上下文的保存不再由操作系统负责,而是通过callback_map由程序代码自己来负责的
操作系统只负责维护阻塞I/O操作与对应的key(也就是overlapped.address这个东西)的关系。程序内的多个并发流程(本例子里只有一个客户端)需要由程序自身通过key和callback_map来自己做调度。
这样状态从多个线程的多个栈上,变成了只有一个线程,但是在线程内部有一个callback_map来维护单线程内多个并发流程的状态。某种程度上来说,相对于多线程是把一些操作系统的上下文保存和调度职责从操作系统内核移到了网络程序里。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/45303.html
摘要:在了解了的实现方式的基础之上,希望能够把流程阻塞的功能在的框架之上实现,从而可以制作一个简单的类似,这样的集群调度工具。我们先来看一个最基本的网络编程的例子这是一个服务器。 接下来,会把Python tulip这个网络库(也就是3.4之后的asyncio)如何实现的进行一些分析。在了解了tulip的实现方式的基础之上,希望能够把流程阻塞的功能在tulip的框架之上实现,从而可以制作一个...
摘要:前面的网络编程的例子使用多进程也是可以实现的其中之后会创建一个子进程。从效率上来说,具有多线程一样的问题,而且内存占用会更高,切换成本也更高。多线程和多进程的版本从代码可读性上来说还是非常不错的,很好懂,从上至下平铺直叙的。 前面的网络编程的例子使用多进程也是可以实现的: import socket import os def main(): listen_sock = s...
摘要:最重要的是每个线程,对应了一个函数的执行。有多个线程同时执行的时候,每个线程的状态是由操作系统内核负责保存在内存中的。在多线程的实现中。并且内核的线程在切换多个线程的时候,线程切换的开销是比较大。 上次的网络编程的例子,改写成多线程的是这样: import socket import thread def main(): listen_sock = socket.socke...
下表比较了Gruvi针对asyncio,gevent和eventlet的一些设计决策和功能。 * 特征 Gruvi Asyncio Gevent Eventlet IO library(依赖包) libuv stdlib libev stdlib / libevent IO abstractionTransports/Protocols Transports/ProtocolsGre...
阅读 2800·2021-10-08 10:12
阅读 3891·2021-09-22 15:45
阅读 2443·2019-08-30 15:52
阅读 2537·2019-08-29 18:44
阅读 2566·2019-08-29 12:37
阅读 1066·2019-08-26 13:36
阅读 2486·2019-08-26 13:34
阅读 1374·2019-08-26 12:20