代码如下:
async def query(self, query, param=None):
conn, cur = await self.getCursor()
try:
await cur.execute(query, param)
return await cur.fetchall()
except:
print('error')
finally:
if cur:
await cur.close()
await self.pool.release(conn)
# mysqlobj 是 aiomysql 连接池对象
result = await asyncio.gather(mysqlobj.query(sql1), mysqlobj.query(sql2), mysqlobj.query(sql3), mysqlobj.query(sql4), mysqlobj.query(sql5))
在我的理解中,如果异步执行的话,这段代码执行的时长应该是这 5 个 sql 中耗时最长的时长,但是测试多次,相对同步执行这 5 个 sql 来说,执行时间并没有显著的提升。各位大佬能指点一下吗?
1
BBCCBB 2021-07-21 16:29:32 +08:00
你这代码逻辑上还是`同步`的, 因为你有 await, 会等待每个 sql 执行完成, 就是说一个 sql 执行完后才会去执行下一步的 sql. 只是你这个 sql 还没执行完的过程中, 线程不会卡在这里,, 而是会去执行其他的异步 task. 等你 await 的 task 返回后再继续执行你这个 task.
异步不是你理解的这样的异步. asyncio 的好处是异步 io 的并发.. |
2
chaleaoch 2021-07-21 16:36:53 +08:00
同一个 async 中的 await 是顺序执行的要不然不乱套了吗?
|
3
ErenJaeger OP @BBCCBB 你的意思是 await asyncio.gather()中执行的 sql,还是会顺序执行下去?而不是同时进行查询,查询完毕后返回? 之前写 node.js 的时候,后续处理的步骤都写在回调过程中了,耗时操作不会阻塞,而是去执行下面的步骤了。
|
4
ErenJaeger OP @chaleaoch await 语法糖的作用应该就是等待异步代码返回结果了吧,类似于回调。我想问的是 asyncio.gather()里面的任务不应该是并发执行的吗
|
5
chaleaoch 2021-07-21 17:23:41 +08:00
@ErenJaeger 是并发的没错, 但是 是 task 并发.
协程的基础是生成器 - - 如果从生成器开始看的话 应该会有更好的理解- - |
6
chaleaoch 2021-07-21 17:26:32 +08:00
如图
是 task1 和 task2 之间 切换 和其他的 await 没关系 我手画的 - - 偷懒没画全 希望我表达清楚了 https://cdn.jsdelivr.net/gh/chaleaoch/CDN@main/images/1626859549049-1626859549045.png |
7
joApioVVx4M4X6Rf 2021-07-21 17:27:51 +08:00
tasks = [asyncio.create_task(mysqlobj.query(sql1)),asyncio.create_task(mysqlobj.query(sql2)),asyncio.create_task(mysqlobj.query(sql3)),asyncio.create_task(mysqlobj.query(sql4)),asyncio.create_task(mysqlobj.query(sql5))]
await asyncio.wait(tasks) 这样试试呢 |
8
BBCCBB 2021-07-21 17:32:55 +08:00
nodejs 继续执行后面是因为你不用 await nodejs 的这个 promise, 而是等他返回结果返回后, 调用你注册的回调 function.去处理 function 果返回
nodejs 里, 如果你后续的执行依赖某个 nodejs 异步函数的结果, 那不还是得等异步函数结束拿到结果后再执行后面的结果吗. 1: var s = asyncFunc(xxx, function (res) { res 是这个函数的返回值. }); // 不需要等待 asyncFunc 返回值, xxx(); // 2: var res = await asyncFunc(xxx); // 需要等待 asyncFunc 返回值 console.log(res) xxx() 类似这两种方式. |
9
pabupa 2021-07-21 17:40:40 +08:00
楼主这种用法没有问题。
|
11
pabupa 2021-07-21 17:42:02 +08:00
我觉得应该是驱动的问题,,,
|
13
ErenJaeger OP @chaleaoch 是呀,就是我指的就是 gather 里面的任务并发执行的话,执行时长应该是这里面最长执行任务的时长,而不是所有任务执行时长的累积
|
14
Vegetable 2021-07-21 17:52:23 +08:00
用法没问题,不过信息还是不够。包括链接池大小和具体时常。甚至说,mysql 的性能是不是瓶颈,都需要考虑。
|
15
ErenJaeger OP @BBCCBB 是的,这两种方式是写法的区别。 我的意思是:
fuction outside(){ asyncFucn1() asyncFunc2() asyncFunc3() asycnFunc4() ...... } 如果 outside 函数等待里面异步函数结束退出的话,执行时长应该是内部异步函数执行时间最长的时长吧。那我同时发起 N 个 sql 查询,整体查询时长应该是 N 个查询 sql 中执行时长最长的时长吧 |
16
ErenJaeger OP @pabupa mix 是 5,max 是 10,按理说应该够了,我调整一下试试
|
17
ErenJaeger OP @v2exblog 试了试,差不多,timeit 测试了下跟同步查询的差不多,就感觉很奇怪
|
18
chaleaoch 2021-07-21 18:00:01 +08:00
@ErenJaeger 换成 100 次查询试一下.
|
19
BBCCBB 2021-07-21 18:09:49 +08:00
sorry, 理解错了.. 光看了你问题里写的 5 个 sql, 我漏看了下面的 gather, 你要问的是 gather 里的这 5 个 task 吧
|
20
BBCCBB 2021-07-21 18:10:31 +08:00
刚好你这个 task 里也是 5 个 sql 的 await.. :(
|
21
ErenJaeger OP @chaleaoch 淦,我调了 1000 次,同步 40 多秒,异步 5 秒多。问题生产环境中一个接口里面不可能会有这么高频次的查询,小频次的查询,同步异步的差距就很不明显
|
22
BBCCBB 2021-07-21 18:12:54 +08:00
次数调大点试试. 看起来没啥问题. 可以把代码贴完整点, 包括 loop.run_until_complete()这一块
|
23
BBCCBB 2021-07-21 18:14:57 +08:00
一个接口不可能这么高, 但是其他的接口也有网络 io, 和你测一个接口 1000 次概念差不多, 并发上去了, asyncio 性能差异就出来了..
如果只是一个接口, 没啥访问, 直接同步搞.. 简单 |
24
ErenJaeger OP @BBCCBB gather 里面是顺序执行的吗?我看官方文档里是这样描述的:
同时运行 aws 序列中的可等待对象。 如果 aws 中的任何 awaitable 是协程,则它会自动安排为任务。 如果所有 awaitable 都成功完成,则结果是返回值的聚合列表。结果值的顺序对应于 aws 中等待的顺序。 如果 return_exceptions 为 False (默认),第一个引发的异常会立即传播到在 gather() 上等待的任务。aws 序列中的其他等待对象不会被取消,而是会继续运行。 如果 return_exceptions 为 True,则将异常视为成功结果,并在结果列表中聚合。 如果 gather() 被取消,所有提交的等待(尚未完成)也将被取消。 如果 aws 序列中的任何 Task 或 Future 被取消,则将其视为引发了 CancelledError - 在这种情况下不会取消 gather() 调用。这是为了防止取消一个提交的任务 /未来导致其他任务 /未来被取消。 |
25
ErenJaeger OP class Pmysql:
def __init__(self): self.conn = None self.pool = None async def initpool(self): try: __pool = await aiomysql.create_pool(minsize=10, maxsize=10, host=Config.host, port=Config.port, user=Config.user, password=Config.password, db='db') return __pool except Exception as e: print(e) print('create connect error.') async def getCursor(self): conn = await self.pool.acquire() cur = await conn.cursor() return conn, cur async def query(self, query, param=None): conn, cur = await self.getCursor() try: await cur.execute(query, param) return await cur.fetchall() except: print('error') finally: if cur: await cur.close() await self.pool.release(conn) async def getAmysqlobj(): mysqlobj = Pmysql() pool = await mysqlobj.initpool() mysqlobj.pool = pool return mysqlobj |
26
BBCCBB 2021-07-21 18:28:32 +08:00
gather 不是顺序执行, 都 asyncio 了, 只要里面 task 不阻塞, 就是异步执行的.
你这个没啥问题, 根据你加大到 1000 次, 差距挺大的, 所以应该是量不够大. |
27
ErenJaeger OP @BBCCBB 其实最大的需求,还是提升响应效率了,这个查询 2s 多能降到 1s 多,甚至不到 1s,就是最高的期望值了,但是测试感觉难以实现
|
28
BBCCBB 2021-07-21 18:44:57 +08:00
asyncio 不能降低你代码里单个 query 方法的耗时, 他要做的是用少量线程就能支撑超高的并发量,, 这个用线程是很难实现的, 单个请求的响应时间并不会变得更快,
|
29
vindurriel 2021-07-21 19:45:01 +08:00 via iPhone
sql 是啥 可以换成 sleep(1) 看是 n 秒返回还是 1 秒多返回
|
30
myCupOfTea 2021-07-22 08:45:58 +08:00
楼主写法没问题,怎么好多人说不是并发 gather 不就是并发吗
|
31
myCupOfTea 2021-07-22 08:47:39 +08:00
但是这类数据库的库,其实异步并发不会比多线程好
因为这类库很多都是底层用的 run_in_excutor,其实就是跑线程池(还是要看具体代码,至少 mongodb 官方的异步库是这样的) |
32
ErenJaeger OP @BBCCBB 是呀,我本来是想着,并发执行多个 sql,这样将整体查询时长降低至最长查询的那个 sql 。但是感觉并没有达到预期的效果
|
33
RockShake 2021-07-22 09:11:24 +08:00
异步并不是多线程啊,异步是模拟 Javascript 的逻辑,将程序操作简化为一个线程,使用这个逻辑可以实现 await 功能,详见: https://ruanyifeng.com/blog/2019/11/python-asyncio.html
|
34
NXzCH8fP20468ML5 2021-07-22 09:21:18 +08:00 1
@ErenJaeger 你对异步的理解错了,异步是让一个餐馆合理分配上菜时间使其能够装下更多的顾客,而不是让每个顾客等待上菜的时间减少。
|
35
www5070504 2021-07-22 09:45:57 +08:00
等待时间应该是最长的那个吧 比原来顺序执行肯定短了不少 但是也不可能减少最长的那个请求的时间
|
36
forbxy 2021-07-22 11:18:37 +08:00
python 的协程,包括这个库只是为了单线程服务器在处理 mysql 不会卡住,可以切到其他用户的连接处理
|
37
niu0619 2021-07-22 14:05:42 +08:00
|
38
ErenJaeger OP @xxfye 如果流程是点菜、做菜、上菜的话。同步应该是来一个人点菜、做菜、上菜,下一个人,点菜、做菜、上菜。。。。这样
异步的话,就是同时面对多个人,如果 1 个人在点菜,不会卡在等待他点菜这个地方,而是去做其他已经点好菜的客户的菜,等到点好菜,在做菜,不知道我这个描述对不对,反正厨师觉得 mmp |
39
ErenJaeger OP @RockShake 确实不是多线程,只是我想在执行多个查询的时候,某个查询网络 IO 的时候,可以发起其他查询。减少访问时间
|
40
ElmerZhang 2021-07-22 16:06:37 +08:00
@ErenJaeger #21
相当于某个 API 的耗时从 40ms 优化到 5ms,在某些高性能场景下,这是非常大的提升了 |