当前位置: 首页 > 面试题库 >

为什么要显式调用asyncio.StreamWriter.drain?

邓欣可
2023-03-14
问题内容

从文档:

写(数据)

Write data to the stream.

This method is not subject to flow control. Calls to write() should be

followed by drain().

协程流失()

Wait until it is appropriate to resume writing to the stream. Example:

writer.write(data)
await writer.drain()

据我了解,

  • 您需要在drain每次调用时write调用。
  • 如果不是我猜,write会阻塞循环线程

那么为什么不写一个自动调用它的协程呢?为什么一个电话write不用排空?我可以想到两种情况

  1. 你想writeclose立即
  2. 您必须在消息完成之前缓冲一些数据。

首先是一个特例,我认为我们可以有一个不同的API。缓冲应在写函数内部处理,应用程序不应在意。

让我提出不同的问题。这样做的缺点是什么?python3.8版本可以有效地做到这一点吗?

async def awrite(writer, data):
    writer.write(data)
    await writer.drain()

注意:draindoc明确声明以下内容:

当没有什么可等待的时,drain()立即返回。

再次阅读答案和链接,我认为这些功能是这样工作的。 注意 :检查接受的答案以获得更准确的版本。

def write(data):
    remaining = socket.try_write(data)
    if remaining:
        _pendingbuffer.append(remaining) # Buffer will keep growing if other side is slow and we have a lot of data

async def drain():
    if len(_pendingbuffer) < BUF_LIMIT:
        return
    await wait_until_other_side_is_up_to_speed()
    assert len(_pendingbuffer) < BUF_LIMIT

async def awrite(writer, data):
    writer.write(data)
    await writer.drain()

那么什么时候使用什么:

  1. 当数据不连续时,就像响应HTTP请求一样。我们只需要发送一些数据,而不关心何时到达数据和内存无关紧要-只需使用write
  2. 与上面相同,但是内存是一个问题,请使用 awrite
  3. 当将数据流传输到大量客户端(例如某些实时流或巨大的文件)时。如果数据在每个连接的缓冲区中重复,则肯定会导致RAM溢出。在这种情况下,请编写一个循环,该循环在每次迭代时都要调用大量数据,然后调用awrite。如果文件很大,loop.sendfile则最好提供。

问题答案:

据我了解,(1)每次调用write时都需要调用rain。(2)如果不是我猜,写会阻塞循环线程

两者都不是正确的,但是这种混乱是可以理解的。write()工作方式如下:

  • 调用write()只会将数据存储到缓冲区中,然后将其留给事件循环以在以后的时间实际将其写出,而无需程序的进一步干预。就应用程序而言,数据在后台写入的速度与另一端能够接收数据的速度一样快。换句话说,每个人都write()将使用所需的尽可能多的OS级别写入来调度要传输的数据,而这些写入实际上是在相应文件描述符可写时发出的。所有这一切都是自动发生的,甚至无需等待drain()

  • write()不是协程,它绝对 不会 阻塞事件循环。

第二个属性听起来方便-您可以拨打write()你需要的地方,甚至从一个功能,这不是async def-但它实际上是一个重大 缺陷
write()。流API公开的写入方式与接收数据的操作系统完全脱钩,因此,如果写入数据的速度快于对等方读取数据的速度,则内部缓冲区将不断增长,并且您将面临内存泄漏drain()解决了该问题:如果写入缓冲区太大,则等待它暂停协程,并os.write()在后台成功执行并且缓冲区缩小后再次恢复协程。

您不需要drain()每次 写操作之后 等待,但是您确实需要偶尔等待它,通常在write()调用该循环的迭代之间等待。例如:

while True:
    response = await peer1.readline()
    peer2.write(b'<response>')
    peer2.write(response)
    peer2.write(b'</response>')
    await peer2.drain()

drain()如果未决的未写入数据量很少,则立即返回。如果数据超过高阈值,drain()将暂停调用协程直到待处理的未写入数据量降至低阈值以下。暂停会导致协程停止从读取数据peer1,这反过来又会导致对等方放慢其向我们发送数据的速度。这种反馈称为背压。

缓冲应在写函数内部处理,应用程序不应在意。

这几乎就是write()现在的工作方式-
它确实可以处理缓冲,并且它使应用程序不管好坏都不在乎。另请参阅此答案以获取其他信息。

解决问题的编辑部分:

再次阅读答案和链接,我认为这些功能就是这样工作的。

write()仍然比那聪明。它不会尝试只写入一次,而是会安排数据继续写入,直到没有数据可写入为止。即使您从未等待也将发生这种情况drain()-应用程序必须做的唯一事情就是让事件循环运行其过程足够长的时间,以将所有内容写出。

一个更正确的伪代码write,并drain可能是这样的:

class ToyWriter:
    def __init__(self):
        self._buf = bytearray()
        self._empty = asyncio.Event(True)

    def write(self, data):
        self._buf.extend(data)
        loop.add_writer(self._fd, self._do_write)
        self._empty.clear()

    def _do_write(self):
        # Automatically invoked by the event loop when the
        # file descriptor is writable, regardless of whether
        # anyone calls drain()
        while self._buf:
            try:
                nwritten = os.write(self._fd, self._buf)
            except OSError as e:
                if e.errno == errno.EWOULDBLOCK:
                    return  # continue once we're writable again
                raise
            self._buf = self._buf[nwritten:]
        self._empty.set()
        loop.remove_writer(self._fd, self._do_write)

    async def drain(self):
        if len(self._buf) > 64*1024:
            await self._empty.wait()

实际的实现更为复杂,因为:

  • 它写在具有自己复杂的流控制的Twisted样式的传输/协议层之上,而不是在;os.write
  • drain()并不是真正地等到缓冲区为空时,才等到达到低水位线;
  • 除了EWOULDBLOCK引发的异常之外,其他异常_do_write都存储在中并重新引发drain()

最后一点是调用的 另一个 很好的理由drain()-实际上由于写入失败而注意到对等端已消失。



 类似资料:
  • 我不喜欢这个模式的地方,就像我添加的链接的评论中提到的那样,一个topping不是一个pizza,所以为什么它要继承pizza类呢? 我心目中的解决方案是在对象中使用对象数组,并使用该数组添加或删除披萨的浇头。 这种解决方案不是比使用decorator模式简单得多吗?为什么我们要考虑在这种情况下使用decorator模式呢?

  • 本文向大家介绍为什么要用 redis ?为什么要用缓存?相关面试题,主要包含被问及为什么要用 redis ?为什么要用缓存?时的应答技巧和注意事项,需要的朋友参考一下 主要从“高性能”和“高并发”这两点来看待这个问题。 高性能: 假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓

  • 问题内容: 在有效的Java(第275页)中,有以下代码段: 捕获中断异常以重新引发它有什么用?为什么不让它飞呢? 问题答案: 简单的答案是,这是一个检查的异常,它不在方法(或方法)的签名中。所以你必须抓住它。一旦发现它,建议您设置为设置中断标志。除非您确实打算压缩中断。

  • 问题内容: 这是我的代码: 如果删除,该表不会更新。但是对于select语句,我不需要那个。我很好奇为什么? 问题答案: 在DB- API 规范要求连接到数据库开始新的事务,默认情况下。您必须确认所做的任何更改,或放弃它们。 请注意,如果数据库支持自动提交功能,则必须首先将其关闭。 纯语句,因为它们从不对数据库进行任何更改,因此不必提交更改。

  • Bootstrapping(引导) 是 Netty 中配置程序的过程,当你需要连接客户端或服务器绑定指定端口时需要使用 Bootstrapping。 如前面所述,Bootstrapping 有两种类型,一种是用于客户端的Bootstrap,一种是用于服务端的ServerBootstrap。不管程序使用哪种协议,无论是创建一个客户端还是服务器都需要使用“引导”。 面向连接 vs. 无连接 请记住,这

  • 本文向大家介绍为什么要用Dubbo?相关面试题,主要包含被问及为什么要用Dubbo?时的应答技巧和注意事项,需要的朋友参考一下 因为是阿里开源项目,国内很多互联网公司都在用,已经经过很多线上考验。内部使用了 Netty、Zookeeper,保证了高性能高可用性。 使用 Dubbo 可以将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,可用于提高业务复用灵活扩展,使前端应用能更快速的响应多

  • C++模板-完整指南,第2版介绍max模板: 它解释了使用

  • 作为一种新兴的虚拟化方式,Docker 跟传统的虚拟化方式相比具有众多的优势。 更高效的利用系统资源 由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker 对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以运行更多数量的应用。 更快速的启动时间 传统的虚拟机技术启动应用服务往往需要数