首页 文章

O_NONBLOCK不会在Python中引发异常

提问于
浏览
0

我正在尝试编写一个"cleaner"程序来释放一个在命名管道中被阻塞的潜在编写器(因为没有读取器从管道中读取) . 但是,当没有写入器被阻止写入管道时,清理器本身 should not 阻塞 . 换句话说,"cleaner"必须立即返回/终止,无论是否有被阻止的作者 .

因此,我搜索了“从命名管道读取Python非阻塞”,并得到了这些:

似乎他们建议只使用 os.open(file_name, os.O_RDONLY | os.O_NONBLOCK) 应该没问题,这并没有弄清楚自己出了什么问题 .

我找到了Linux手册页(http://man7.org/linux/man-pages/man2/open.2.html),O_NONBLOCK的解释似乎与他们的建议一致,但与我在机器上的观察不一致...

如果它是相关的,我的操作系统是 Ubuntu 14.04 LTS 64-bit .

这是我的代码:

import os
import errno

BUFFER_SIZE = 65536

ph = None
try:
    ph = os.open("pipe.fifo", os.O_RDONLY | os.O_NONBLOCK)
    os.read(ph, BUFFER_SIZE)
except OSError as err:
    if err.errno == errno.EAGAIN or err.errno == errno.EWOULDBLOCK:
        raise err
    else:
        raise err
finally:
    if ph:
        os.close(ph)

(不知道如何做Python语法高亮...)

最初只有第二个 raise ,但我发现 os.openos.read 虽然没有阻塞,但是真的知道作者会写多少到缓冲区!如果非阻塞 read 没有引发异常,我怎么知道何时停止读取?


2016年8月8日更新:

这似乎是满足我需求的解决方法/解决方案:

import os
import errno

BUFFER_SIZE = 65536

ph = None
try:
    ph = os.open("pipe.fifo", os.O_RDONLY | os.O_NONBLOCK)
    while True:
        buffer = os.read(ph, BUFFER_SIZE)
        if len(buffer) < BUFFER_SIZE:
            break
except OSError as err:
    if err.errno == errno.EAGAIN or err.errno == errno.EWOULDBLOCK:
        pass # It is supposed to raise one of these exceptions
    else:
        raise err
finally:
    if ph:
        os.close(ph)

它将循环 read . 每次读取内容时,它会将读取的内容大小与指定的 BUFFER_SIZE 进行比较,直到达到EOF(然后编写器将解锁并继续/退出) .

我仍然想知道为什么在 read 中没有引发异常 .


2016年10月8日更新:

为了说清楚,我的总体目标是这样的 .

我的主程序(Python)有一个线程作为读者 . 它通常在命名管道上阻塞,等待“命令” . 有一个编写器程序(Shell脚本),它将在每次运行中向同一个管道写一个单行“命令” .

在某些情况下,作者在主程序启动之前或主程序终止之后启动 . 在这种情况下,编写器将阻塞等待读者的管道 . 这样,如果稍后我的主程序启动,它将立即从管道中读取以从被阻止的编写器获得“命令” - 这不是我想要的 . 我希望我的程序忽略在它之前开始的作家 .

因此,我的解决方案是,在我的读者线程初始化期间,我执行非阻塞读取以释放编写器,而不是真正执行他们试图写入管道的“命令” .

1 回答

  • 0

    此解决方案不正确 .

    while True:
        buffer = os.read(ph, BUFFER_SIZE)
        if len(buffer) < BUFFER_SIZE:
            break
    

    这实际上并不会读取所有内容,只有在读取部分内容后才能读取 . 请记住:您只能保证使用常规文件填充缓冲区,在所有其他情况下,可以在EOF之前获取部分缓冲区 . 执行此操作的正确方法是循环,直到达到文件的实际结尾,这将给出长度为0的读取 . 文件结尾表示没有编写器(它们都已退出或关闭了fifo) .

    while True:
        buffer = os.read(ph, BUFFER_SIZE)
        if not buffer:
            break
    

    但是,面对非阻塞IO,这将无法正常工作 . 事实证明,这里完全不需要非阻塞IO .

    import os
    import fcntl
    
    h = os.open("pipe.fifo", os.O_RDONLY | os.O_NONBLOCK)
    # Now that we have successfully opened it without blocking,
    # we no longer want the handle to be non-blocking
    flags = fcntl.fcntl(h, fcntl.F_GETFL)
    flags &= ~os.O_NONBLOCK
    fcntl.fcntl(h, fcntl.F_SETFL, flags)
    try:
        while True:
            # Only blocks if there is a writer
            buf = os.read(h, 65536)
            if not buf:
                # This happens when there are no writers
                break
    finally:
        os.close(h)
    

    导致此代码阻止的唯一方案是,如果有一个活动的编写器已打开fifo但未写入它 . 从你所描述的情况来看,听起来并非如此 .

    非阻塞IO不会这样做

    你的程序想要做两件事,具体取决于具体情况:

    • 如果没有作家,请立即返回 .

    • 如果有写入器,则从FIFO读取数据直到写入器完成 .

    非阻塞 read() 对任务#1没有任何影响 . 无论您是否使用 O_NONBLOCKread() 将立即返回情况#1 . 所以唯一的区别在于情况#2 .

    在情况#2中,您的程序的目标是从编写器中读取整个数据块 . 这正是阻止IO工作的方式:它等待编写器完成,然后 read() 返回 . 如果操作可以't complete immediately, which is the opposite of your program'的目标 - 即等待操作完成,则非阻塞IO的重点是提前返回 .

    如果您使用非阻塞 read() ,在情况#2中,您的程序有时会在作者完成作业之前提前返回 . 或者也许你的程序会在从FIFO读取一半命令后返回,而另一半(现已损坏)则在那里 . 您的问题表达了这种担忧:

    如果非阻塞读取不会引发异常,我应该怎么知道何时停止读取?

    您知道何时停止读取,因为 read() 在所有写入器关闭管道时返回零字节 . (方便的是,如果首先没有编写者,也会发生这种情况 . )遗憾的是,如果编写者在完成时没有关闭管道的末尾会发生这种情况 . 如果编写器在完成时关闭管道,则更简单,更直接,因此这是推荐的解决方案,即使您需要稍微修改编写器 . 如果作者无论出于何种原因都无法关闭管道,那么解决方案就更复杂了 .

    非阻塞 read() 的主要用例是,如果您的程序还有其他任务需要完成,而IO在后台继续运行 .

相关问题