【问题标题】:Performance of asyncio异步的性能
【发布时间】:2017-05-02 00:27:11
【问题描述】:

我正在尝试熟悉 asyncio,所以我决定编写一个数据库客户端。但是,性能与同步代码完全匹配。我敢肯定这是我对一个概念的误解。有人可以解释我在做什么错吗?

请看下面的代码示例:

class Connection:
    def __init__(self, reader, writer, loop):
        self.futures = deque()

        # ...

        self.reader_task = asyncio.async(self.recv_data(), loop=self.loop)

    @asyncio.coroutine
    def recv_data(self):
        while 1:
            try:
                response = yield from self.reader.readexactly(4)
                size, = struct.unpack('I', response)
                response = yield from self.reader.readexactly(size)

                # ...                

                future = self.futures.popleft()

                if not future.cancelled():
                    future.set_result(response)

            except Exception:
                break

    def send_data(self, data):
        future = asyncio.Future(loop=self.loop)
        self.futures.append(future)

        self.writer.write(data)

        return future


loop = asyncio.get_event_loop()


@asyncio.coroutine
def benchmark():
    connection = yield from create_connection(loop=loop, ...)

    for i in range(10000):
        yield from connection.send_data(...)


s = time.monotonic()

loop.run_until_complete(benchmark())

e = time.monotonic()
print('Requests per second:', int(10000 / (e - s)))

提前致谢。

【问题讨论】:

    标签: python python-asyncio


    【解决方案1】:

    您拨打send_data 的方式有误。现在,你得到了这个:

    @asyncio.coroutine
    def benchmark():
        connection = yield from create_connection(loop=loop, ...)
    
        for i in range(10000):
            yield from connection.send_data(...)
    

    通过在 for 循环中使用 yield from,您正在等待从 send_data 返回的 future 产生结果,然后再继续下一个调用。这使您的程序基本上是同步的。您想拨打所有电话到send_data,然后然后等待结果:

    @asyncio.coroutine
    def benchmark():
        connection = yield from create_connection(loop=loop, ...)
        yield from asyncio.wait([connection.send_data(..) for _ in range(10000)])
    

    【讨论】:

    • 完美,谢谢。据我所知,这与为每个“send_data”调用创建一个任务一样吗?
    • @Andrew 或多或少,尽管您仍然需要向benchmark 添加代码以等待每个Task 完成。实际上,无论如何,我相信对asyncio.wait 的调用会将传递给它的所有协程对象在内部转换为Task 实例。
    • 是的,你们都是对的。 asyncio.wait 将包装任何传入的协程对象或在 Task 未来中等待。自己用loop.create_taskasyncio.ensure_future 包装它们的唯一行为可能会将它们安排在循环中,但不会在它们最终完成时阻止协程代码的执行。您仍然需要 yield from 那些 Tasks 或将它们传递给 asyncio.wait 之类的东西。
    【解决方案2】:

    pythonasyncio 模块是单线程的:

    该模块为使用协程编写单线程并发代码、通过套接字和其他资源多路复用 I/O 访问、运行网络客户端和服务器以及其他相关原语提供了基础设施。

    This question 解释了为什么 asyncio 可以比线程慢,但简而言之:asyncio 使用单个线程来执行您的代码,因此即使您有多个协程,它们也都是串行执行的。线程池用于执行一些回调和 I/O。由于 GIL,线程也可以串行执行用户代码,尽管 I/O 操作可以同步运行。

    使用 asyncio 并没有比串行执行的代码有改进的原因,是因为事件循环一次只运行一个协程。

    【讨论】:

    • OPs 代码应该仍然比同步代码执行得更好,因为它是 I/O 绑定的。有一个线程并不重要——当 I/O 在一个协程中运行时,其他协程可以执行。您链接到的问题有点特殊 - 它使用的是getaddrinfo,实际上并未使用异步 I/O 实现。它使用一个小的ThreadPool 代替,这限制了可用的并行量。这使得它比常规的多线程代码慢,但它仍然比同步代码快,这就是这个问题的意义所在。
    • @dano 那我错了。我理解得不够好。我正在为你的答案投票。
    • 没问题。异步框架是一个相当奇怪的概念。您在答案中写的最后一句话实际上基本上是正确的,但它是由于编码错误而发生的,而不是asyncio的限制。
    猜你喜欢
    • 2014-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-06
    • 2020-08-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多