线程的数据结构比进程更轻量级,同一个进程可以容纳多个线程。进程作为管理线程的容器,用于管理进程所需的资源。OS级别的线程可以被分配到不同的CPU核心同时运行。
CPython环境下,一个Python进程中,只允许有一个线程处于运行状态。
Python中 time.sleep 是阻塞的,但在多线程编程中,time.sleep 并不会阻塞其他线程。
多线程是被OS调度的,调度策略都是抢占式。多线程的主要问题是竞态条件。
非阻塞方式
sock.setblocking(False)让socket上阻塞调用都改为非阻塞的方式。
OS将I/O状态的变化都封装成了事件,如可读事件、可写事件。并且提供了专门的系统模块让应用程序可以接收事件通知。这个模块就是select。让应用程序可以通过select注册文件描述符和回调函数。当文件描述符的状态发生变化时,select 就调用事先注册的回调函数。
select因其算法效率比较低,后来改进成了poll,再后来又有进一步改进,BSD内核改进成了kqueue模块,而Linux内核改进成了epoll模块。
这四个模块的作用都相同,暴露给程序员使用的API也几乎一致,区别在于kqueue 和 epoll 在处理大量文件描述符时效率更高。
回调(callback)
python的selectors模块是对select/poll/epoll/kqueue的封装
等待时间通知的循环,称之为事件循环
selector机制是设计用来解决大量并发连接的。当系统中有大量非阻塞调用,能随时产生事件的时候,selector机制才能发挥最大的威力。
在单线程内用事件循环+回调
事件循环+回调,单线程内实现异步编程
“事件循环+回调” 到 “asyncio的原生协程模式”
为了防止栈撕裂,异常必须以数据的形式返回,而不是直接抛出异常,然后每个回调中需要检查上次调用的返回值,以防错误吞没。
异步编程的最大的困难:异步任务何时执行完毕,对异步调用的返回结果做什么操作;
程序得知道当前所处的状态,而且要将这个状态在不同的回调之间延续下去;
协作式多任务,协程;每个协程都有自己的栈帧,所以知道自己处于什么状态,协程之间可以协作那自然可以通知别的协程。
协程
coroutine,协作式例程
非抢占式的多任务子例程的概括,可以允许有多个入口点在例程中确定的位置来控制程序的暂停与恢复执行。
例程,编程语言定义的可被调用的代码段,为了完成某个特定功能而封装在一起的一系列指定。一般的编程语言都用称为函数或方法的代码结构来体现。
基于生成器的协程
generator,每一次迭代之间,会暂停执行,继续下一次迭代的时候还不会丢失当前的状态。
PEP342 生成器可以通过yield暂停执行和向外返回数据,也可以通过send()向生成器内发送数据,还可以通过throw()向生成器内抛出异常以便随时暂停生成器的运行。
未来对象
异步调用执行完的时候,就把结果放在未来对象里面。
未来对象有一个result属性,用于存放未来的执行结果。还有个set_result()方法,是用于设置result的,并且会给result绑定值后运行事先给future添加的回调。回调是通过未来对象的add_done_callback()方法添加。
yield from 改进生成器协程
PEP 380 引入yield from
作用:1.让嵌套生成器不必通过循环迭代yield,而是直接yield from
def gen_one():subgen = range(10)yield from subgendef gen_two():subgen(10)for item in subgen:yield item
两种处理自生成器的方式是等价的
2.在子生成器与原生成器的调用者之间打开双向通道,两者可以通讯
def gen():yield from subgen()def subgen():while True:x=yield x+1def main():g = gen() next(g) # 驱动生成器g执行第一个yieldretval = g.send(1) # 看似向生成器gen()发送数据print(retrval) # 返回2g.throw(StopIteration) # 看似向gen()抛出异常
关键字yield from在gen()内部为subgen()和main()开辟了通信通道。main()里可以直接将数据1发送给subgen(),subgen()也可以将计算后的数据2返回到main()里,main()里也可以直接向subgen()抛入异常以终止subgen()。
yield可以作用于普通Python对象,而yield from却不行,yield from只能作用于生成器
Python3.3引入yield from新语法后,就不再推荐yield去做协程。
asyncio
PEP 3156引入,提供了基于协程做异步I/O编写单线程并发代码的基础设施。
核心组件:事件循环(EventLoop),协程(Coroutine),任务(Task),未来对象(Future)
@asyncio.coroutine用于装饰使用了yield from的函数,以标记其为协程。
async await
PEP 492引入,原生协程。async/await 和yield from底层复用共同的实现,相互兼容
对比生成器版的协程,使用asyncio库后变化很大:
- 没有了yield 或 yield from,而是async/await
- 没有了自造的loop(),取而代之的是asyncio.get_event_loop()
- 无需自己在socket上做异步操作,不用显式地注册和注销事件,aiohttp库已经代劳
- 没有了显式的 Future 和 Task,asyncio已封装
- 更少量的代码,更优雅的设计