python异步函数 python异步执行命令( 五 )


这样一来,每一次查询都要顺序的新建数据库连接,都要阻塞在数据库返回结果的过程中 。当前端提交大量查询请求时,查询效率肯定是很低的 。
第一次改进
之前的模块里,问题最大的就是第一步——建立数据库连接套接字了 。如果能够一次性建立连接,之后查询能够反复服用这个连接就好了 。
所以,首先应该把数据库查询模块作为一个单独的守护进程去执行 , 而前端app作为主进程响应用户的点击操作 。那么两条进程怎么传递消息呢?翻了几天Python文档 , 终于构思出来:用队列queue作为生产者(web前端)向消费者(数据库后端)传递任务的渠道 。生产者 , 会与SQL命令一起,同时传递一个管道pipe的连接对象 , 作为任务完成后,回传结果的渠道 。确保,任务的接收方与发送方保持一致 。
【python异步函数 python异步执行命令】作为第二个问题的解决方法 , 可以使用线程池来并发获取任务队列中的task,然后执行命令并回传结果 。
第二次改进
第一次改进的效果还是很明显的,不用任何测试手段 。直接点击页面链接,可以很直观地感觉到反应速度有很明显的加快 。
但是对于第二个问题,使用线程池还是有些欠妥当 。因为,CPython解释器存在GIL问题,所有线程实际上都在一个解释器进程里调度 。线程稍微开多一点,解释器进程就会频繁的切换线程,而线程切换的开销也不小 。线程多一点,甚至会出现“抖动”问题(也就是刚刚唤醒一个线程 , 就进入挂起状态,刚刚换到栈帧或内存的上下文 , 又被换回内存或者磁盘),效率大大降低 。也就是说,线程池的并发量很有限 。
试过了多进程、多线程 , 只能在单个线程里做文章了 。
Python中的asyncio库
Python里有大量的协程库可以实现单线程内的并发操作,比如Twisted、Gevent等等 。Python官方在3.5版本里提供了asyncio库同样可以实现协程并发 。asyncio库大大降低了Python中协程的实现难度,就像定义普通函数那样就可以了,只是要在def前面多加一个async关键词 。async def函数中,需要阻塞在其他async def函数的位置前面可以加上await关键词 。
import asyncio
async def wait():
await asyncio.sleep(2)
async def execute(task):
process_task(task)
await wait()
continue_job()
async def函数的执行稍微麻烦点 。需要首先获取一个loop对象,然后由这个对象代为执行async def函数 。
loop = asyncio.get_event_loop()
loop.run_until_complete(execute(task))
loop.close()
loop在执行execute(task)函数时,如果遇到await关键字,就会暂时挂起当前协程,转而去执行其他阻塞在await关键词的协程,从而实现协程并发 。
不过需要注意的是,run_until_complete()函数本身是一个阻塞函数 。也就是说,当前线程会等候一个run_until_complete()函数执行完毕之后 , 才会继续执行下一部函数 。所以下面这段代码并不能并发执行 。
for task in task_list:
loop.run_until_complete(task)
对与这个问题,asyncio库也有相应的解决方案:gather函数 。
loop = asyncio.get_event_loop()
tasks = [asyncio.ensure_future(execute(task))
for task in task_list]
loop.run_until_complete(asyncio.gather(*tasks))
loop.close()
当然了,async def函数的执行并不只有这两种解决方案,还有call_soon与run_forever的配合执行等等 , 更多内容还请参考官方文档 。
Python下的I/O多路复用
协程,实际上 , 也存在上下文切换,只不过开销很轻微 。而I/O多路复用则完全不存在这个问题 。

推荐阅读