O_NONBLOCK不会在Python

时间:2016-08-09 06:10:44

标签: python linux system-calls

我正在尝试编写一个“更干净”的程序来释放一个在命名管道中被阻塞的潜在编写器(因为没有读取器从管道中读取)。但是,当没有编写器被阻止写入管道时,清理器本身不应阻止。换句话说,“清洁工”必须立即返回/终止,无论是否有被封锁的作家。

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

  1. How to read named FIFO non-blockingly?
  2. fifo - reading in a loop
  3. What conditions result in an opened, nonblocking named pipe (fifo) being "unavailable" for reads?
  4. Why does a read-only open of a named pipe block?
  5. 似乎他们建议只使用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位

    这是我的代码:

    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脚本),它将在每次运行中向同一个管道写一个单行“命令”。

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

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

2 个答案:

答案 0 :(得分: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不会这样做

您的计划希望根据具体情况做两件事:

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

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

  3. 非阻止read()对任务#1的无效。无论您是否使用O_NONBLOCKread()都会在情况#1中立即返回 。所以唯一的区别在于情况#2。

    在情况#2中,你的程序的目标是从作者那里读取整个数据块。这正是阻止IO工作的方式:它等待编写器完成,然后read()返回。如果操作无法立即完成,那么非阻塞IO的重点就是提前返回,这与您的程序的目标相反 - 即等到操作完成。

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

      

    如果非阻塞读取没有引发异常,我该如何知道何时停止读取?

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

    非阻塞read()的主要用例是,当IO在后台运行时,您的程序还有其他任务需要完成。

答案 1 :(得分:0)

在POSIX C程序中,如果read()尝试从空管道或FIFO特殊文件中读取,则它将具有以下结果之一:

  • 如果没有进程打开要写入的管道,则read()返回0表示文件结束。
  • 如果某个进程打开了用于写入的管道,并且O_NONBLOCK设置为1,则read()返回-1并将errno设置为EAGAIN。
  • 如果某个进程打开了要写入的管道,并且O_NONBLOCK设置为0,则read()块(即不返回),直到写入了一些数据,或者所有其他拥有该管道的进程关闭了该管道开放写作。

因此,首先检查是否还有写程序仍在打开fifo进行写操作。如果没有,则读取将得到一个空字符串,并且没有异常。否则,将引发异常