【问题标题】:gobject io monitoring + nonblocking readsgobject io 监控 + 非阻塞读取
【发布时间】:2009-10-18 23:38:03
【问题描述】:

我在 python 中使用 io_add_watch 监视器时遇到了问题(通过 gobject)。我想在每次通知后对整个缓冲区进行非阻塞读取。这是代码(稍微缩短了一点):

class SomeApp(object):

   def __init__(self):
      # some other init that does a lot of stderr debug writes
      fl = fcntl.fcntl(0, fcntl.F_GETFL, 0)
      fcntl.fcntl(0, fcntl.F_SETFL, fl | os.O_NONBLOCK)
      print "hooked", gobject.io_add_watch(0, gobject.IO_IN | gobject.IO_PRI, self.got_message, [""])
      self.app = gobject.MainLoop()

   def run(self):
      print "ready"
      self.app.run()

   def got_message(self, fd, condition, data):
      print "reading now"
      data[0] += os.read(0, 1024)
      print "got something", fd, condition, data
      return True

gobject.threads_init()
SomeApp().run()

这是诀窍 - 当我在未激活调试输出的情况下运行程序时,我没有收到 got_message 调用。当我先向stderr写很多东西时,问题就消失了。如果除了这段代码中可见的打印之外我不写任何东西,我就不会收到 stdin 消息信号。另一个有趣的事情是,当我尝试通过strace(检查是否有任何我错过的 fcntl / ioctl 调用)在启用了 stderr 调试的情况下运行相同的应用程序时,问题再次出现。

简而言之:如果我先在没有 strace 的情况下向 stderr 写很多东西,io_watch 就可以了。如果我用 strace 写了很多,或者根本不写io_watch 不起作用。

“其他一些初始化”部分需要一些时间,所以如果我在看到“钩子 2”输出之前输入一些文本,然后在“就绪”之后按“ctrl+c”,则调用 get_message 回调,但是读取调用抛出 EAGAIN,因此缓冲区似乎是空的。

stdin相关的strace日志:

ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
fcntl(0, F_GETFL)                       = 0xa002 (flags O_RDWR|O_ASYNC|O_LARGEFILE)
fcntl(0, F_SETFL, O_RDWR|O_NONBLOCK|O_ASYNC|O_LARGEFILE) = 0
fcntl(0, F_GETFL)                       = 0xa802 (flags O_RDWR|O_NONBLOCK|O_ASYNC|O_LARGEFILE)

有人对这里发生的事情有一些想法吗?


编辑:另一个线索。我试图重构应用程序以在不同的线程中进行读取并通过管道将其传回。它“有点”有效:

...
      rpipe, wpipe = os.pipe()
      stopped = threading.Event()
      self.stdreader = threading.Thread(name = "reader", target = self.std_read_loop, args = (wpipe, stopped))
      self.stdreader.start()
      new_data = ""
      print "hooked", gobject.io_add_watch(rpipe, gobject.IO_IN | gobject.IO_PRI, self.got_message, [new_data])

   def std_read_loop(self, wpipe, stop_event):
      while True:
         try:
            new_data = os.read(0, 1024)
            while len(new_data) > 0:
               l = os.write(wpipe, new_data)
               new_data = new_data[l:]
         except OSError, e:
            if stop_event.isSet():
               break
            time.sleep(0.1)
...

令人惊讶的是,如果我只是将相同的文本放入新管道中,一切都会开始工作。问题是:

  • 根本没有“注意到”第一行 - 我只得到第二行和以下行
  • 很丑

也许这会让其他人知道为什么会发生这种情况?

【问题讨论】:

    标签: python input glib gobject


    【解决方案1】:

    这听起来像是一种竞争条件,在这种情况下设置回调会有一些延迟,或者环境发生变化会影响您是否可以设置回调。

    在您致电io_add_watch() 之前,我会仔细查看会发生什么。例如 Python fcntl 文档说:

    此模块中的所有函数都采用 文件描述符 fd 作为他们的第一个 争论。这可以是一个整数文件 描述符,例如返回 sys.stdin.fileno() 或文件对象, 比如 sys.stdin 本身,它 提供一个 fileno() 它返回一个 真正的文件描述符。

    当您假设 STDIN 将具有 FD == 0 时,显然这不是您正在做的事情。我会先更改它然后再试一次。

    另一件事是,如果 FD 已经被阻塞,那么您的进程可能正在等待,而其他非阻塞进程正在运行,因此根据您首先执行的操作存在时间差异。如果你重构 fcntl 的东西,让它在程序启动后立即完成,甚至在导入 GTK 模块之前,会发生什么?

    我不确定我是否理解为什么使用 GTK GUI 的程序首先要从标准输入中读取数据。如果您实际上是在尝试捕获另一个进程的输出,则应使用 subprocess 模块设置管道,然后在管道上使用io_add_watch(),如下所示:

    proc = subprocess.Popen(command, stdout = subprocess.PIPE)
    gobject.io_add_watch(proc.stdout, glib.IO_IN, self.write_to_buffer )
    

    同样,在本例中,我们确保在调用 io_add_watch( 之前打开了有效的 FD。

    通常,当使用gobject.io_add_watch() 时,它会在gobject.MainLoop() 之前调用。例如,下面是一些使用io_add_watch 捕获IO_IN 的工作代码。

    【讨论】:

    • 在这种情况下,我需要的描述符保证为 0。我需要将它作为描述符处理/它不是子进程。不幸的是,移动 fnctl 没有用。我没有使用 gtk,只是 gobject / gst 集成。但无论如何都是好的建议。不幸的是,我的代码仍然无法正常工作。
    • 我不知道你为什么要加入线程,因为这只会增加复杂性并为竞争条件创造更多机会。看看这里的 sn-ps,特别是使用 GStreamer pyneo.org/documentation/snippets 实现播放器的那个
    • 这是另一个 GStreamer 播放器示例de.pastebin.ca/raw/933910 我真的认为您需要将其简化为可行的东西,然后从那里开始构建,而不是反过来。
    【解决方案2】:

    documentation 表示您应该从回调中返回 TRUE,否则它将从事件源列表中删除。

    【讨论】:

    • 它在真实代码中返回 True。不过没关系 - 如果它没有被调用,它根本不会被调用 - 甚至一次。
    【解决方案3】:

    如果在任何 stderr 输出之前先挂钩回调会发生什么?启用调试输出后是否仍会调用它?

    另外,我想你可能应该在你的处理程序中重复调用os.read(),直到它没有提供任何数据,以防>1024字节在调用之间准备好。

    您是否尝试过在后台线程中使用select 模块来模拟gio 功能?那样有用吗?这是什么平台,你在处理什么样的FD? (文件?套接字?管道?)

    【讨论】:

    • 更改初始化顺序没有帮助。我尝试先刷新描述符(同时使用刷新和读取),但这没有帮助。我在 linux 上运行它,fd 是一个由生成我的应用程序的程序打开的管道。它适用于后台线程+阻塞读取,但我已经在同一个程序中将 glib 的 io 用于其他 fd,所以我不想对每个输入使用不同的方法。
    猜你喜欢
    • 1970-01-01
    • 2017-09-18
    • 1970-01-01
    • 1970-01-01
    • 2012-04-22
    • 1970-01-01
    • 1970-01-01
    • 2010-11-17
    • 2014-11-17
    相关资源
    最近更新 更多