目录
一、概念
协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文切换技术。基于单线程来实现并发,即只用一个主线程(可利用的cpu只有一个)情况下实现并发,并发的本质:切换+保存状态。
协程的目的,想要在单线程下实现并发,这里的并发指的是多个任务看起来是同时运行的。
缺点:无法利用CPU多核,一旦协程出现阻塞,将会阻塞整个线程。
二、使用场景
单纯地切换反而会降低代码运行效率,协程适用于网络请求,io阻塞异步场景。
三、同步与异步的区别
同步,是所有的操作都做完,才返回给用户结果。即写完数据库之后,在相应用户,用户体验不好。同步程序的瓶颈在于漫长的I/O等待,想要提高程序效率必须减少I/O等待时间,从提高程序的局部性着手。
异步,不用等所有操作等做完,就相应用户请求。即先相应用户请求,然后慢慢去写数据库,用户体验较好。
四、协程实现方式
1.greenlet实现协程
greenlet、gevent早期模块(了解即可)
遇到IO阻塞时会自动切换任务
from greenlet import greenlet
def func1():
print(1) # 第1步:输出 1
gr2.switch() # 第3步:切换到 func2 函数
print(2) # 第6步:输出 2
gr2.switch() # 第7步:切换到 func2 函数,从上一次执行的位置继续向后执行
def func2():
print(3) # 第4步:输出 3
gr1.switch() # 第5步:切换到 func1 函数,从上一次执行的位置继续向后执行
print(4) # 第8步:输出 4
gr1 = greenlet(func1)
gr2 = greenlet(func2)
gr1.switch() # 第1步:去执行 func1 函数
import gevent
def eat(name):
print('%s eat 1' %name)
gevent.sleep(2)
print('%s eat 2' %name)
def play(name):
print('%s play 1' %name)
gevent.sleep(1)
print('%s play 2' %name)
g1=gevent.spawn(eat,'egon')
g2=gevent.spawn(play,name='egon')
g1.join()
g2.join()
#或者gevent.joinall([g1,g2])
print('执行完成')
2.yield关键字
实际开发基本用不到(了解即可),基本都是用封装好的框架
def func1():
yield 1
yield from func2()
yield 2
def func2():
yield 3
yield 4
f1 = func1()
for item in f1:
print(item)
3.asyncio模块(异步爬虫)
aiohttp:Client Quickstart — aiohttp 3.8.4 documentationa
aiohttp发送post请求
async with session.post(url, data=json.dumps(params), headers=headers, verify_ssl=False) as response:
output = await response.json()
import aiohttp
import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
async def fetch(session, url):
print("发送请求:", url)
async with session.get(url, verify_ssl=False) as response:
content = await response.content.read()
file_name = url.rsplit('_')[-1]
with open(file_name, mode='wb') as file_object:
file_object.write(content)
print('下载完成',url)
async def main():
async with aiohttp.ClientSession() as session:
url_list = [
'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg',
'https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg',
'https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg'
]
tasks = [ asyncio.create_task(fetch(session, url)) for url in url_list ]
await asyncio.wait(tasks)
if __name__ == '__main__':
asyncio.run( main() )
之前旧的写法
import aiohttp
import asyncio
async def fetch(session, url):
print("发送请求:", url)
async with session.get(url, verify_ssl=False) as response:
content = await response.content.read()
file_name = url.rsplit('_')[-1]
with open(file_name, mode='wb') as file_object:
file_object.write(content)
print('下载完成', url)
async def main():
async with aiohttp.ClientSession() as session:
url_list = [
'https://www3.autoimg.cn/newsdfs/g26/M02/35/A9/120x90_0_autohomecar__ChsEe12AXQ6AOOH_AAFocMs8nzU621.jpg',
'https://www2.autoimg.cn/newsdfs/g30/M01/3C/E2/120x90_0_autohomecar__ChcCSV2BBICAUntfAADjJFd6800429.jpg',
'https://www3.autoimg.cn/newsdfs/g26/M0B/3C/65/120x90_0_autohomecar__ChcCP12BFCmAIO83AAGq7vK0sGY193.jpg'
]
tasks = [asyncio.create_task(fetch(session, url)) for url in url_list]
await asyncio.wait(tasks)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
# asyncio.run( result ) # python3.7
zip函数
zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。Python zip() 函数 | 菜鸟教程
import asyncio
async def download(url, t):
print('开始下载', url, t)
await asyncio.sleep(1)
print('下载完成', url, t)
async def main():
urls = [
'http://www.baidu.com/',
'http://www.weiwei.com/',
'http://www.weihubj.shop'
]
ts = [1, 2, 3]
# task列表
tasks = [asyncio.create_task(download(url, t)) for url, t in zip(urls, ts)]
# 等待任务都结束
await asyncio.wait(tasks)
if __name__ == '__main__':
asyncio.run(main())
4.事件循环
注意python3.7之后,直接ayncio.run就行,里面封装了事件loop
import asyncio
async def func():
print("开始执行")
result = func()
loop = asyncio.get_event_loop()
loop.run_until_complete(result)
# asyncio.run( result ) # python3.7
5.uvloop事件循环性能提升
是asyncio的事件循环的替代方案。事件循环 > 默认asyncio的事件循环。性能提升,重点
import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
# 编写asyncio的代码,与之前写的代码一致。
# 内部的事件循环自动化会变为uvloop
asyncio.run(...)
6.await与async关键字
await + 可等待的对象(协程对象、Future、Task对象 -> IO等待)
Tasks用于并发调度协程,通过asyncio.create_task(协程对象)的方式创建Task对象
Task继承Future,Task对象内部await结果的处理基于Future对象来的。(Future对象了解即可)
import asyncio
async def others():
print("start")
await asyncio.sleep(2)
print('end')
return '返回值'
async def func():
print("执行协程函数内部代码")
# 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。
response1 = await others()
print("IO请求结束,结果为:", response1)
response2 = await others()
print("IO请求结束,结果为:", response2)
asyncio.run( func() )
函数名前面加上关键字async,表示协程函数;协程函数()不会执行,表示一个协程对象。
如func()表示一个协程对象,不会去执行函数。
五、asyncio + 不支持异步的模块
效果和协程是一样的,但是线程等待耗费的资源更多,不得已建议才用这种混合的方式
这种混合使用的还不如直接用线程池去做(个人感觉)
requests只能发送同步请求;aiohttp只能发送异步请求;httpx既能发送同步请求
import asyncio
import requests
import json
import uuid
import time
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
def func(query, name):
print(name)
url = "http://httpbin.org/post"
data = {
'name': query
}
headers = {
'content-type': 'application/json',
'User-Agent': "Mozilla / 5.0(Windows NT 10.0;Win64;x64) AppleWebKit / 537.36(KHTML, likeGecko) Chrome / 96.0.4664 .93 Safari / 537.36",
}
response = requests.post(url=url, data=json.dumps(data), headers=headers)
return response.text
async def send_post(query):
print("开始发送请求", query)
loop = asyncio.get_event_loop()
# query,name是传给func函数的参数,
name = 'weige'
future_obj = loop.run_in_executor(None, func, query, name)
response = await future_obj
print('请求完成')
print(response)
if __name__ == '__main__':
start_time = time.time()
query_list = ['kongtiao', 'xiyiji', 'yifu']
tasks = [send_post(query) for query in query_list]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
end_time = time.time()
print('执行时间', end_time - start_time)

本文介绍了协程的概念,它是一种用户态的上下文切换技术,用于在单线程中实现并发。文章详细讨论了同步与异步的区别,并通过greenlet和yield关键字展示了协程的实现方式。此外,还特别提到了asyncio模块在异步爬虫中的应用,以及如何使用uvloop提升事件循环的性能。最后,文章指出在asyncio中与不支持异步的模块混合使用的情况。
547

被折叠的 条评论
为什么被折叠?



