当前位置: 首页 > 科技观察

如何在Python线程中运行协程

时间:2023-03-17 01:20:29 科技观察

在一文理解Python异步编程的基本原理中,我们提到如果在异步代码中包含一段非常耗时的同步代码,那么异步代码就会卡住。那么有没有办法让同步代码和异步代码看起来同时运行呢?方法是使用事件循环的.run_in_executor()方法。我们来看看Python官方文档中的说法[1]:那么怎么用呢?我们以这个非常耗时的递归方式计算斐波那契数列的函数为例:defsync_calc_fib(n):ifnin[1,2]:return1returnssync_calc_fib(n-1)+sync_calc_fib(n-2)asyncdefcalc_fib(n):result=sync_calc_fib(n)print(f'第{n}项计算完成,结果为:{result}')returnresult我们现在需要用到aiohttp访问一个网页,延迟5秒计算斐波那契数列的第36项。首先我们看第36项单独计算需要5秒:我们看一下计算斐波那契数列和请求网站这两个异步任务是否“并行”放在一起,实际时间是两个任务时间叠加:具体原因我在上一篇文章中已经解释过了。现在,我想让两个任务“同时运行”,所以我可以像这样修改代码:127.0.0.1:8000/sleep/{sleep_time}')resp_json=awaitresp.json()打印(resp_json)defsync_calc_fib(n):ifnin[1,2]:return1returnssync_calc_fib(n-1)+sync_calc_fib(n-2)defsync_fib(n):result=sync_calc_fib(n)print(f'第{n}项计算完成,结果为:{result}')returnresultasyncdefmain():start=time.perf_counter()loop=asyncio.get_event_loop()withThreadPoolExecutor(max_workers=4)asexecutor:tasks_list=[loop.run_in_executor(executor,calc_fib,36),asyncio.create_task(request(5))]awaitasyncio.gather(*tasks_list)end=time.perf_counter()print(f'总耗时:{end-start}')asyncio.run(main())运行效果如下图所示:5秒内,计算斐波那契数列和websi请求5秒延迟的请求已完成。实现这样的改造,关键代码是:loop.run_in_executor(executor,calc_fib,36)其中loop是主线程的事件循环,用于在同一个线程中调度多个协程。Executor是我们使用ThreadPoolExecutor(max_workers=4)创建的一个线程池,里面有4个线程,calc_fib是一个耗时同步函数,36是传入calc_fib的参数。loop.run_in_executor(executor,calc_fib,36)的意思是:将calc_fib函数放入线程池中运行,在线程池中添加一个回调函数,这个回调函数会在执行后将结果保存在下一个事件循环中。请注意,上图中红色箭头对应的calc_fib是一个同步函数,请与上一篇文章中的异步函数区分开来。run_in_executor的第二个参数需要是同步函数的函数名。在上面的例子中,我们创建了一个有4个线程的线程池。所以这个线程池最多允许4个阻塞同步函数“并行”。