asyncio 是干什么的?
异步网络操作
高并发
协程
包含各种特定系统实现的模块化事件循环
传输和协议抽象
对tcp,upd,ssl,子进程,延时调用以及其他的具体支持
模仿futures模块但适用于事件循环使用的Future类
基于yield from的协议和任务,可以让你用顺序的方式编写并发代码
必须使用一个将产生阻塞io的调动时,有接口可以把这个事件转移到线程池
模仿threading模块中的同步原语,可以在单线程协程内调用
总结: asyncio库是一个python实现了原生协程异步等功能的库,并且可以让我们使用顺序的方式编程,不用考虑事件循环以及事件循环的变量传递问题。他是Python解决异步io编程的一整套解决方案
使用的方式:
事件循环+驱动协程+io多路复用
event_loop 事件循环:程序开启一个无限的循环,程序员会把一些函数注册到事件循环上。
当满足事件发生的时候,调用相应的协程函数。
coroutine 协程:协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,
而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。
task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态。
future: 代表将来执行或没有执行的任务的结果。它和task上没有本质的区别
async/await 关键字:python3.5 用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。
@asyncio.coroutine
装饰器@asyncio.coroutine
把一个generator(生成器)标记为coroutine(协程)类型,然后,
就把这个coroutine(协程)扔到eventloop(事件循环)中去执行。
yield from可以让我们方便的调用另一个generator(生成器),
由于asyncio.sleep(1)也是一个coroutine(协程)
所以线程不会等待asyncio.sleep而是直接中断并执行下一个消息循环,当asyncio.sleep返回的时候,
线程就在yield from拿到返回值,此处是None
然后执行下一个语句
把asyncio.sleep(1)看成是一个耗时1秒的IO操作。在此期间主线程没有等待,
而是去执行eventloop其他可以执行的coroutine因此可以实现并发执行
promise 对象有三种状态:成功(Fulfilled)失败(Rejected)等待(Pending)
promise 不配合 async await 时,使用 .then() .catch() 处理成功和失败情况是目前的常规方案。
async 表示这是一个async函数,await只能用在这个函数里面。async 对象也是一个 promise 对象。
await 表示在这里等待promise返回结果了,再继续执行。
await 后面跟着的应该是一个promise对象(当然,其他返回值也没关系,不过那样就没有意义了…)
很多库的接口返回 promise 对象,await 后赋值给一个变量后使用其 resolve 的值。
[例如](http://mongoosejs.com/docs/api.html#query_Query-exec)
注意三点,promise 对象的状态,promise 对象上的方法(then,catch),promise 对象返回的值。
promise 是当时为了解决回调地狱的解决方案,也是当前处理异步操作最流行和广泛使用的方案,
async 和 await 最为当前的终极方案两只之间还有一些过渡方案。
yield from 用法
带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,
import asyncio # 导入异步协程库
import time # 导入时间库
@asyncio.coroutine # 将get_html()即生成器(generator)装饰为协程类型
def get_html(url):
while 1:
start_time = time.time()
print(start_time)
print('start l', url)
yield from asyncio.sleep(5) # 由于asyncio.sleep(2)也是一个coroutine(协程)
print('end 2', url) # 所以线程不会等待asyncio.sleep而是直接中断并执行下一个消息循环
end_time = time.time() # 当asyncio.sleep返回的时候,再去执行终端后未完成的语句
print(end_time)
print("-----------")
if __name__ == '__main__':
loop = asyncio.get_event_loop() # 获取EventLoop事件
tasks = [get_html("asd"), get_html("asd")] #创建两个协程事件
loop.run_until_complete(asyncio.wait(tasks)) #运行事件直到结束,asyncio.wait执行这个协程
#(输出)
1543936141.3279934
start l asd
1543936141.3289928
start l asd
end 2 asd
1543936146.3291001
-----------
1543936146.3291001
start l asd
end 2 asd
1543936146.3291001
-----------
1543936146.3291001
start l asd
end 2 asd
1543936151.329194
-----------
1543936151.3296947
start l asd
end 2 asd
1543936151.3296947
-----------
中断
>原生协程用法
import asyncio #导入异步协程库
import time #导入时间库
#协程中是不能使用同步阻塞编程的
async def get_html(url):
print('start get url',url)
await asyncio.sleep(2)
print('end get url')
if __name__ == '__main__':
start_time = time.time()
#事件循环,代替自己写的loop
loop = asyncio.get_event_loop()
loop.run_until_complete(get_html("www.baidu.com"))
print('times',time.time()-start_time) #计算时间
#(输出)
start get url www.baidu.com
end get url
times 2.0032832622528076
多个任务提交
import asyncio
import time
@asyncio.coroutine
def get_html(times):
print('start get url'.format(times))
yield from asyncio.sleep(times)
print('end get url {
}'.format(times))
if __name__ == '__main__':
start_time = time.time()
loop = asyncio.get_event_loop()
tasks = list()
for i in range(1, 10):
tasks.append(get_html(i))
loop.run_until_complete(asyncio.wait(tasks))
import asyncio
import time
#协程中是不能使用同步阻塞编程的
async def get_html(url):
print('start get url',url)
await asyncio.sleep(2)
print('end get url')
if __name__ == '__main__':
start_time = time.time()
#事件循环,代替自己写的loop
loop = asyncio.get_event_loop()
tasks = [get_html('www.baidu.com') for i in range(10)]
loop.run_until_complete(asyncio.wait(tasks))
print('times',time.time()-start_time)
简单进行一个爬虫
#coding:utf-8
import time,asyncio,aiohttp
#aiohttp则是基于asyncio实现的HTTP框架,底层使用了io多路复用。
num = 0
async def fetch(session,url):
async with session.get(url) as response: #session.get(url)获取页面
return await response.text() #返回页面解码后的信息
async def main(url,semaphore):
async with semaphore: #这里进行执行asyncio.Semaphore
async with aiohttp.ClientSession() as session: 创建了一个命名为session的aiohttp.ClientSession 对象
html = await fetch(session, url) #相当于 yield from
global num
num += 1
print(html)
print('------------{
}'.format(num))
async def run():
semaphore = asyncio.Semaphore(500) # 限制并发量为500,这里windows需要进行并发限制,
#不然将报错:ValueError: too many file descriptors in select()
to_get = [main("http://blog.jobbole.com/{
}/".format(_),semaphore) for _ in range(114364,120000)] #总共1000任务
await asyncio.wait(to_get)
#Python中对于无需关注其实际含义的变量可以用_代替,这就和for i in range(5)一样,因为这里我们对i并不关心,所以用_代替仅获取值而已。
if __name__ == '__main__':
# now=lambda :time.time()
loop = asyncio.get_event_loop()
loop.run_until_complete(run())
loop.close()
ValueError: too many file descriptors in select()的原因
因为asyncio内部用到了select,而select就是那个什么系统打开文件数是有限度的,上面的代码一次性将处理url的函数作为任务扔进了一个超大的List中,这就引起了错误,用这种形式无法写大规模爬虫。
解决:如上面的代码。回调一下,设置并发量, semaphore = asyncio.Semaphore(500) #Windows在500以下。
获取async返回值
使用 asyncio.ensure_future(get_html('www.baidu.com'))
futures类来给出结果的期望在循环调用完成后获取返回结果
使用 loop.create_task(get_html('www.baidu.com'))
直接添加到循环中来表示想获取到结果
使用 add_done_callback(call_back)
可以添加回调函数,通知一个函数表示这个函数已经完成
import asyncio
import time
# 协程中是不能使用同步阻塞编程的
async def get_html(url):
print('start get url', url)
await asyncio.sleep(2)
print('end get url')
return 'get_futures'
# 函数必须要使用到future,就是完成好的函数
def call_back(future):
print('tasks compete')
if __name__ == '__main__':
start_time = time.time()
# 事件循环,代替自己写的loop
loop = asyncio.get_event_loop()
# 1.使用future,获取future期望,传递给loop
# get_future = asyncio.ensure_future(get_html('www.baidu.com'))
get_future = loop.create_task(get_html('www.baidu.com'))
# 把结果返回给callback函数
get_future.add_done_callback(call_back)
loop.run_until_complete(get_future)
print('times', time.time() - start_time)
print(get_future.result())
wait 和 gather
wait和gather都表示等待任务完成
gather可以接受过个任务组来执行
import asyncio
import time
#协程中是不能使用同步阻塞编程的
async def get_html(url):
print('start get url',url)
await asyncio.sleep(2)
print('end get url')
if __name__ == '__main__':
start_time = time.time()
#事件循环,代替自己写的loop
loop = asyncio.get_event_loop()
tasks = [get_html('www.baidu.com') for i in range(10)]
tasks2 = [get_html('www.google.com') for i in range(10)]
# loop.run_until_complete(asyncio.wait(tasks))
loop.run_until_complete(asyncio.gather(*tasks,*tasks2))
print('times',time.time()-start_time)
或者使用
import asyncio
import time
#协程中是不能使用同步阻塞编程的
async def get_html(url):
print('start get url',url)
await asyncio.sleep(2)
print('end get url')
async def get_html2(url):
print('start get url',url)
await asyncio.sleep(4)
print('end get url')
if __name__ == '__main__':
start_time = time.time()
#事件循环,代替自己写的loop
loop = asyncio.get_event_loop()
t1 = [get_html('www.baidu.com') for i in range(10)]
t2 = [get_html2('www.google.com') for i in range(10)]
tasks = asyncio.gather(*t1)
tasks2 = asyncio.gather(*t2)
# loop.run_until_complete(asyncio.wait(tasks))
loop.run_until_complete(asyncio.gather(tasks,tasks2))
print('times',time.time()-start_time)
取消协程
import asyncio
import time
#协程中是不能使用同步阻塞编程的
async def get_html(url):
print('start get url',url)
await asyncio.sleep(2)
print('end get url')
async def get_html2(url):
print('start get url',url)
await asyncio.sleep(4)
print('end get url')
if __name__ == '__main__':
start_time = time.time()
#事件循环,代替自己写的loop
loop = asyncio.get_event_loop()
t1 = [get_html('www.baidu.com') for i in range(10)]
t2 = [get_html2('www.google.com') for i in range(10)]
tasks = asyncio.gather(*t1)
tasks2 = asyncio.gather(*t2)
# loop.run_until_complete(asyncio.wait(tasks)
try:
loop.run_until_complete(asyncio.gather(tasks,tasks2))
except KeyboardInterrupt:
print(tasks2.cancel())
loop.close()
print('times',time.time()-start_time)
或者使用
import asyncio
import time
#协程中是不能使用同步阻塞编程的
async def get_html(url):
print('start get url',url)
await asyncio.sleep(2)
print('end get url')
async def get_html2(url):
print('start get url',url)
await asyncio.sleep(4)
print('end get url')
if __name__ == '__main__':
start_time = time.time()
#事件循环,代替自己写的loop
loop = asyncio.get_event_loop()
t1 = [get_html('www.baidu.com') for i in range(10)]
t2 = [get_html2('www.google.com') for i in range(10)]
# tasks = asyncio.gather(*t1)
# tasks2 = asyncio.gather(*t2)
# loop.run_until_complete(asyncio.wait(tasks)
try:
loop.run_until_complete(asyncio.wait(t1))
except KeyboardInterrupt:
for task in asyncio.Task.all_tasks():
print(task.cancel())
loop.stop()
print('times',time.time()-start_time)
stop,close的区别
stop是等待事件循环里面的所有协程执行完成之后停止
close 是会清空所有队列和线程池等。
import asyncio
import time
def call_back(x):
time.sleep(3)
print('get',x)
def stoploop(loop):
loop.close()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.call_soon(call_back,32)
loop.call_soon(stoploop,loop)
loop.run_forever()
call_soon和停止循环
解释:
1. call_soon不会立刻执行,而是等待loop的最新一次循环取执行
2. 我们可以调用loop.stop()去停止协程的事件循环
import asyncio
import time
def call_back(x):
time.sleep(3)
print('get',x)
def stoploop(loop):
loop.stop()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.call_soon(call_back,32)
loop.call_soon(stoploop,loop)
loop.run_forever()
call_later会让程序延迟(秒)执行
import asyncio
import time
def call_back(x):
time.sleep(3)
print('get',x)
def stoploop(loop):
loop.stop()
time.sleep(4)
loop.close()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.call_later(3,call_back,32)
loop.call_later(2, call_back, 31)
# loop.call_later(2,stoploop,loop)
loop.run_forever()
call_at loop的执行时间戳,loop自己的运行时间戳
import asyncio
import time
def call_back(x):
time.sleep(3)
print('get',x)
def stoploop(loop):
loop.stop()
time.sleep(4)
loop.close()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
now = loop.time()
loop.call_at(now+3,call_back,32)
loop.call_at(now+2, call_back, 31)
loop.run_forever()
loop.call_soon_threadsafe() 线程安全
说明: 如果是线程的调用,可以保护线程中的变量的安全
说明: 使用asyncio的前提是库本身支持,就像http请求,库是没有支持的,如果我们想使用httprequest 那么只好使用多线程,以至于整个主线程不阻塞
import asyncio
from concurrent.futures import ThreadPoolExecutor
import time
def call_back(x):
time.sleep(3)
print('get',x)
def stoploop(loop):
loop.stop()
time.sleep(4)
loop.close()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
ex = ThreadPoolExecutor(max_workers=10)
tasks = list()
for i in range(20):
tasks.append(loop.run_in_executor(ex,call_back,3))
loop.run_until_complete(asyncio.wait(tasks))
async自带的有tcp协议,我们不用自己去注册事件,直接使用即可
一次性返回所有结果
import socket
# 使用tcp连接
import asyncio
async def get_html():
# 返回读写
reader, writer = await asyncio.open_connection('www.baidu.com', 80)
data = "GET {
} HTTP/1.1\r\nHost:{
}\r\nConnection:close\r\nUser-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36\r\n\r\n".format(
'/moist-master/rimi_linux_mysql/blob/master/tcp_ip_socket/notes/http_proto.md', 'www.baidu.com').encode('utf8')
# 发送数据
writer.write(data)
print('sended')
datas = []
# for 循环不会阻塞
async for data in reader:
datas.append(data.decode('utf8'))
res = "".join(datas)
return res
if __name__ == '__main__':
loop = asyncio.get_event_loop()
tasks = []
for i in range(10):
tasks.append(asyncio.ensure_future(get_html()))
loop.run_until_complete(asyncio.wait(tasks))
for i in tasks:
print(i.result())
逐个返回结果
import socket
# 使用tcp连接
import asyncio
async def get_html():
# 返回读写
reader, writer = await asyncio.open_connection('www.baidu.com', 80)
data = "GET {
} HTTP/1.1\r\nHost:{
}\r\nConnection:close\r\nUser-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36\r\n\r\n".format(
'/moist-master/rimi_linux_mysql/blob/master/tcp_ip_socket/notes/http_proto.md', 'www.baidu.com').encode('utf8')
# 发送数据
writer.write(data)
print('sended')
datas = []
# for 循环不会阻塞
async for data in reader:
datas.append(data.decode('utf8'))
res = "".join(datas)
return res
async def start():
tasks = []
for i in range(10):
tasks.append(asyncio.ensure_future(get_html()))
for task in asyncio.as_completed(tasks):
res = await task
print(res)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(start())