一、回顾
1、同步
所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不会返回。按照这个定义,其实绝大多数函数都是同步调用。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。
# 举例:
# 1、multiprocessing.Pool下的apply #发起同步调用后,就在原地等着任务结束,根本不考虑任务是在计算还是在io阻塞,总之就是一股脑地等任务结束
# 2、concurrent.futures.ProcessPoolExecutor().submit(func,).result()
# 3、concurrent.futures.ThreadPoolExecutor().submit(func,).result()
2、异步
异步的概念和同步相对。当一个异步功能调用发出后,调用者不能立刻得到结果。当该异步功能完成后,通过状态、通知或回调来通知调用者。如果异步功能用状态来通知,那么调用者就需要每隔一定时间检查一次,效率就很低(有些初学多线程编程的人,总喜欢用一个循环去检查某个变量的值,这其实是一 种很严重的错误)。如果是使用通知的方式,效率则很高,因为异步功能几乎不需要做额外的操作。至于回调函数,其实和通知没太多区别。
# 举例:
# 1、multiprocessing.Pool().apply_async() #发起异步调用后,并不会等待任务结束才返回,相反,会立即获取一个临时结果(并不是最终的结果,可能是封装好的一个对象)。
# 2、concurrent.futures.ProcessPoolExecutor(3).submit(func,)
# 3、concurrent.futures.ThreadPoolExecutor(3).submit(func,)
3、阻塞
阻塞调用是指调用结果返回之前,当前线程会被挂起(如:遇到IO操作)。函数只有在得到结果之后才会将阻塞的线程激活。有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。
# 举例:
# 1、同步调用:apply一个累计1亿次的任务,该调用会一直等待,直到任务返回结果为止,但并未阻塞住(即便是被抢走cpu的执行权限,那也是处于就绪态);
# 2、阻塞调用:当socket工作在阻塞模式的时候,如果没有数据的情况下调用recv函数,则当前线程就会被挂起,直到有数据为止。
4、非阻塞
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前也会立刻返回,同时该函数不会阻塞当前线程。
5、总结
同步与异步针对的是函数/任务的调用方式:同步就是当一个进程发起一个函数(任务)调用的时候,一直等到函数(任务)完成,而进程继续处于激活状态。而异步情况下是当一个进程发起一个函数(任务)调用的时候,不会等函数返回,而是继续往下执行当,函数返回的时候通过状态、通知、事件等方式通知进程任务完成。
阻塞与非阻塞针对的是进程或线程:阻塞是当请求不能满足的时候就将进程挂起,而非阻塞则不会阻塞当前进程。
二、异步编程
1、asyncio 模块
asyncio 模块是 Python 中实现异步的一个模块,该模块在 Python3.4 的时候发布,async 和 await 关键字在 Python3.5 中引入。因此,想要使用asyncio模块,建议 Python 解释器的版本不要低于 Python3.5 。
2、事件循环
所谓的事件循环,我们可以把它当作是一个 while 循环,这个 while 循环在循环生命周期内运行并执行一些任务,在特定的条件下结束循环。
在编写程序的时候可以通过如下代码来获取和创建事件循环:
import asyncio
loop = asyncio.get_event_loop()
3、协程 & 异步编程
首先我们来看一下 协程函数,什么是协程函数呢?直白的讲,定义为如下形式的函数,我们可以称之为协程函数,如下代码所示:
async def fn():
pass
知道了什么是协程函数,接下来我们再来看一下什么是协程对象,所谓的协程对象就是调用协程函数之后返回的对象,我们称之为 协程对象,如下代码所示:
res = fn()
注意事项:调用协程函数时,函数内部的代码不会执行,只是会返回一个协程对象!
基本应用
在编写程序的时候,如果想要执行协程函数内部的代码,通过 函数名() 调用函数是不可以的,需要 事件循环 和 协程对象 配合才能实现,如下代码所示:
import asyncio
async def fn():
print('协程函数内部的代码')
def main():
# 调用协程函数,返回一个协程对象
res = fn()
# 执行协程代码的方式一
# todo:1、创建一个事件循环
# loop = asyncio.get_event_loop()
# todo:2、将协程当作任务提交到事件循环的任务列表中,协程执行完成之后终止
# loop.run_until_complete(res)
# 执行协程代码的方式二
# 解析:第二种方式在本质上和第一种方式是相同的,其内部先创建事件循环,然后执行 run_until_complete
# 但是要注意:该方式只支持 Python3.7+ 的解释器,因为该方式在 Python3.7 加入的。
asyncio.run(res)
if __name__ == '__main__':
main()
这个过程可以简单理解为:将 协程函数 当做任务添加到 事件循环 的任务列表,然后事件循环检测列表中的协程函数 是否已准备就绪(默认可理解为就绪状态),如果准备就绪则执行其内部代码。
await:
await 是一个 只能 在协程函数中使用的关键字,用于当协程函数遇到IO操作的时候挂起当前协程(任务),当前协程挂起过程中,事件循环可以去执行其他的协程(任务),当前协程IO处理完成时,可以再次切换回来执行 await 之后的代码,怎么理解呢?请看如下代码:
示例:
import asyncio
async def fn():
print('协程函数内部的代码')
# 遇到IO操作之后挂起当前协程(任务),等IO操作完成之后再继续往下执行。
# 当前协程挂起时,事件循环可以去执行其他协程(任务)
response = await asyncio.sleep(2) # 模拟遇到了IO操作
print(f'IO请求结束,结果为:{response}')
def main():
# 调用协程函数,返回一个协程对象
res = fn()
# 执行协程函数
asyncio.run(res)
if __name__ == '__main__':
main()
'''
运行结果:
协程函数内部的代码
IO请求结束,结果为:None
'''
示例2;
import asyncio
async def other_tasks():
print('start')
await asyncio.sleep(2) # 模拟遇到了IO操作
print('end')
return '返回值'
async def fn():
print('协程函数内部的代码')
# 遇到IO操作之后挂起当前协程(任务),等IO操作完成之后再继续往下执行。
# 当前协程挂起时,事件循环可以去执行其他协程(任务)
response = await other_tasks() # 模拟执行其他协程任务
print(f'IO请求结束,结果为:{response}')
def main():
# 调用协程函数,返回一个协程对象
res = fn()
# 执行协程函数
asyncio.run(res)
if __name__ == '__main__':
main()
'''
运行结果:
协程函数内部的代码
start
end
IO请求结束,结果为:返回值
'''
示例3:
import asyncio
async def other_tasks():
print('start')
await asyncio.sleep(2) # 模拟遇到了IO操作
print('end')
return '返回值'
async def fn():
print('协程函数内部的代码')
# 遇到IO操作之后挂起当前协程(任务),等IO操作完成之后再继续往下执行。
# 当前协程挂起时,事件循环可以去执行其他协程(任务)
respnse1 = await other_tasks()
print(f'IO请求结束,结果为:{respnse1}')
respnse2 = await other_tasks()
print(f'IO请求结束,结果为:{respnse2}')
def main():
# 调用协程函数,返回一个协程对象
cor_obj = fn()
# 执行协程函数
asyncio.run(cor_obj)
if __name__ == '__main__':
main()
'''
运行结果:
协程函数内部的代码
start
end
IO请求结束,结果为:返回值
start
end
IO请求结束,结果为:返回值
'''
上述的所有实例都只是创建了一个任务,即:事件循环的任务列表中只有一个任务,所以在IO等待时无法演示切换到其他任务效果。
在程序中想要创建多个任务对象,需要使用Task对象来实现。
Task 对象
Tasks 用于并发调度协程,通过 asyncio.create_task(协程对象) 的方式创建 Task 对象,这样可以让协程加入事件循环中等待被调度执行。除了使用 asyncio.create_task() 函数以外,还可以用低层级的loop.create_task() 或 ensure_future() 函数。并且不建议手动实例化 Task 对象。
本质上是将协程对象封装成 Task 对象,并将协程立即加入事件循环,同时追踪协程的状态。
注意事项:asyncio.create_task() 函数在 Python3.7 中被加入。在 Python3.7 之前,可以改用低层级的 asyncio.ensure_future() 函数。
示例:
import asyncio
async def other_tasks():
print('start')
await asyncio.sleep(2) # 模拟遇到了IO操作
print('end')
return '返回值'
async def fn():
print('fn开始')
# 创建协程,将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。
task1 = asyncio.create_task(other_tasks())
# 创建协程,将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。
task2 = asyncio.create_task(other_tasks())
print('fn结束')
# 当执行某协程遇到IO操作时,会自动化切换执行其他任务。
# 此处的await是等待相对应的协程全都执行完毕并获取结果
response1 = await task1
response2 = await task2
print(response1, response2)
def main():
asyncio.run(fn())
if __name__ == '__main__':
main()
'''
运行结果:
fn开始
fn结束
start
start
end
end
返回值 返回值
'''
示例2:
import asyncio
async def other_tasks():
print('start')
await asyncio.sleep(2) # 模拟遇到了IO操作
print('end')
return '返回值'
async def fn():
print('fn开始')
# 创建协程,将协程封装到一个Task对象中并立即添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。
task_lis = [
asyncio.create_task(other_tasks()),
asyncio.create_task(other_tasks()),
]
print('fn结束')
# 当执行某协程遇到IO操作时,会自动化切换执行其他任务。
# 此处的await是等待所有协程执行完毕,并将所有协程的返回值保存到done
# 如果设置了timeout值,则意味着此处最多等待的秒,完成的协程返回值写入到done中,未完成则写到pending中。
done, pending = await asyncio.wait(task_lis, timeout=None)
print(done)
print(pending)
def main():
asyncio.run(fn())
if __name__ == '__main__':
main()
'''
运行结果:
fn开始
fn结束
start
start
end
end
{<Task finished name='Task-3' coro=<other_tasks() done, defined at /Users/liumingyang/PycharmProjects/async_test/test.py:4> result='返回值'>, <Task finished name='Task-2' coro=<other_tasks() done, defined at /Users/liumingyang/PycharmProjects/async_test/test.py:4> result='返回值'>}
set()
'''
异步迭代器
1.什么是异步迭代器?
实现了 aiter() 和 anext()方法的对象。anext 必须返回一个 awaitable 对象。async for会处理异步迭代器的 anext()方法所返回的可等待对象,直到其引发一个 StopAsyncIteration异常。
2. 什么是异步可迭代对象?
可在 async for语句中被使用的对象。必须通过它的 aiter()方法返回一个 asynchronous iterator 。
import asyncio
class Reader:
""" 自定义异步迭代器(同时也是异步可迭代对象) """
def __init__(self):
self.count = 0
async def readline(self):
self.count += 1
if self.count == 100:
return None
return self.count
def __aiter__(self):
return self
async def __anext__(self):
val = await self.readline()
if val is None:
raise StopAsyncIteration
return val
async def fn():
# 创建异步可迭代对象
async_iter = Reader()
# async for 必须放在async def 函数内,否则语法错误。
async for item in async_iter:
print(item)
asyncio.run((fn()))
5、异步上下文管理器
此种对象通过定义 aenter() 和 aexit() 方法来对 async with 语句中的环境进行控制。
import asyncio
class AsyncContextManager:
def __init__(self):
self.conn = None
async def do_something(self):
# 异步操作数据库
return 123
async def __aenter__(self):
# 异步链接数据库
self.conn = await asyncio.sleep(1)
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
# 异步关闭数据库链接
await asyncio.sleep(1)
async def fn():
async with AsyncContextManager() as f:
res = await f.do_something()
print(res)
asyncio.run(fn())
6、小结
在程序中只要看到 async 和 await 关键字,其内部就是基于协程实现的异步编程,这种异步编程是通过一个线程在IO等待时间去执行其他任务,从而实现并发。