Python 异步程序和同步程序的交互

Python 异步程序和同步程序的交互

问题的提出

       在I/O等待处理较多的 Pyhthon 程序中,使用 asycio 异步程序可以大大提高程序的运行效率,并简化程序的开发。我们在开发中使用异步编程时,需要接收来自原有同步程序队列的数据。为此,我们在网络查找了与异步编程有关的资料,发现 Python 的事件循环中有将一个同步函数转换为线程或进程的方法,返回一个 feature 对象后就可以异步执行;与此同时,Python 3.9 以后,asyncio 库中也有了 to_thread 方法实现同样的功能,而且看起来更为简洁。本文的内容就是我们试用这些方法的情况。

官方文档的描述

       事件循环的上述功能在官网中有描述其链接是:https://docs.python.org/zh-cn/3/library/asyncio-eventloop.html#executing-code-in-thread-or-process-pools。可以参见其中的“在线程或进程池中执行代码”一节。具体的方法为:
        awaitable loop.run_in_executor(executor, func, *args)
其中的 tunc 即是同步的函数或方法。
       Python 3.9 以后,asyncio 本身也有了类似的方法:
       coroutine asyncio.to_thread(func, /, *args, **kwargs)
       具体详见下述官方网页:https://docs.python.org/zh-cn/3/library/asyncio-task.html 的“在线程中运行"一节。

代码实例

      下面的例子演示了同步方法和异步方法的混合执行。主程序执行了两个线程,一个是启动异步的主程序,另一个是同步的测试驱动程序。
       异步程序 def async main() 中,并发执行两个异步任务,其中一个是执行异步程序 factorial,循环定时打印阶乘的每个因子,最后输出阶乘的值,这个方法摘自官方的示例程序;另一个是转换后执行的同步程序 get_queue_test(), 从 test_queue 中循环阻塞读取 4 次,每次读到消息就打印出来,最后返回一个 ‘got’ 字符串。
       同步测试驱动程序每隔1秒钟向 test_queue 中放入一个数字。
       下附全部代码,Python 3.9 以后的解释器可以正确执行。如程序中那样,注释掉下面一句,Python 3.7 以后的解释器都可以运行。
        L = await asyncio.gather(factorial(“C”, 4), asyncio.to_thread(get_queue_test))

import time
import asyncio
import queue
import threading


test_queue = queue.Queue()


async def factorial(name, number):
    f = 1
    for i in range(2, number + 1):
        print(f"Task {name}: Compute factorial({number}), currently i={i}...")
        await asyncio.sleep(1)
        f *= i
    print(f"Task {name}: factorial({number}) = {f}")
    return f


def get_queue_test():
    f = 0
    while f < 4:
    	# 读取队列,阻塞方式
        f = test_queue.get()
        print(f"task sync: {f} received ")
    return 'got'


async def sync_trans():
    loop = asyncio._get_running_loop()
    fut = loop.run_in_executor(None, get_queue_test)
    got = await fut
    return got



# 生成两个并发运行的任务并执行
async def main():
    # 这一句调用了sync_trans,是事件循环的 executor 的例子, 在 sync_trans 中转换和执行。
    L = await asyncio.gather(factorial("C", 4), sync_trans())
    
    # 这一句是使用 to_thread 方法的例子,用 to_thread 直接转换和执行 get_queue_test
    # L = await asyncio.gather(factorial("C", 4), asyncio.to_thread(get_queue_test))
    print(L)


def run_main():
    asyncio.run(main())


# 用于测试的驱动,同步程序,向消息队列中放入消息,用的是queue.Queue,而不是 asyncio 的队列。
def put_to_queue():
    for i in range(0, 5):
        time.sleep(2)
        test_queue.put(i)


if __name__ == '__main__':

    print('\n\rbegining of the testing!')
    
    # 异步处理线程
    t1 = threading.Thread(target=run_main)
    
    # 测试驱动线程
    t2 = threading.Thread(target=put_to_queue)
    
    t1.start()
    t2.start()
    t1.join()
    t2.join()

    print('end of the testing\n\r')

代码执行结果如下:



begining of the testing!
Task C: Compute factorial(4), currently i=2...
task to thread: 0 received
Task C: Compute factorial(4), currently i=3...
task to thread: 1 received
Task C: Compute factorial(4), currently i=4...
Task C: factorial(4) = 24
task to thread: 2 received
task to thread: 3 received
task to thread: 4 received
[24, 'got']
end of the testing

从执行结果可以看出,get_test_queue 的阻塞 get 操作没有影响 factorial 的执行。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值