如何解除阻塞已删除命名管道上的线程阻塞?

Posted

技术标签:

【中文标题】如何解除阻塞已删除命名管道上的线程阻塞?【英文标题】:How to unblock a thread blocking on a deleted named pipe? 【发布时间】:2019-08-02 19:25:49 【问题描述】:

我有一个 Python 程序,它将命名管道用于各种目的。 每个管道都由不同的线程管理,因此不会阻塞主线程。

假设我有一个线程阻塞了对open('in', 'rb') 的调用,其中in 是命名管道的相对路径。 如果我想关闭我的程序,我会使用这样的东西来解除我的线程与另一个线程的阻塞:

with suppress(OSError):
    fd = os.open('in', O_WRONLY | O_NONBLOCK)
    os.close(fd)

这只是以写入模式打开管道,以便open 上的线程阻塞可以继续,然后关闭它。我使用O_NONBLOCK 来避免在另一个线程已经终止的情况下阻塞(并忽略潜在的OSError)。

这工作正常,直到有人决定删除命名管道 in 而我的线程阻塞在 open 上。 在这种情况下,我不能使用我的“尝试以非阻塞模式打开管道并关闭它”方法,因为管道在文件系统上不再可见(并且非阻塞打开只会创建一个全新的管道)。

除了杀死线程之外,这个问题的正确解决方案是什么? 请注意,我无法阻止其他进程删除管道,并且权限也无济于事(删除进程可以以 root 身份运行)。

【问题讨论】:

最坏的情况我可以使用非阻塞打开来轮询管道,而不是在打开它时显式阻塞,但我发现阻塞方法更优雅,因为轮询需要浪费 CPU 周期并引入了时间因素。 您的问题听起来像一个 XY 问题(到底为什么有人会首先使用命名管道?)。但是如果你设置了一个信号处理程序,发送信号将中断阻塞的 open() 系统调用,让你的程序在处理完 EINTR 错误后继续。所以你可以发送一个信号而不是那个打开/关闭技巧。 @mosvy 我必须使用命名管道(说来话长)。在 Python (3.7) 中可以做到这一点吗?文档说:“Python 信号处理程序总是在 Python 主线程中执行,即使信号是在另一个线程中接收到的。这意味着信号不能用作线程间通信的手段。” 信号处理程序不需要做任何事情;只是为了中断 open() 系统调用。请参阅“答案”(我不是 python 程序员,所以这可能不是最好的方法)。另一个想法是使用 O_RDWR 打开命名管道(这将不会阻塞)——但在这种情况下,您无法通过 EOF 确定作者何时关闭了管道的末端。跨度> 似乎没有办法a)在python中获取真正的线程ID(为了向特定线程发送信号所必需的)和b)在python3中处理EINTR - 所有系统调用都将是由解释器手动重新启动(尽管在 python2 中仍然可以)。因此,唯一的解决方法是以读写模式打开命名管道(所有现代系统都支持)。 【参考方案1】:

我已经通过使用os.open('in', O_RDONLY | O_NONBLOCK) 解决了我的问题,即使管道的另一端没有写入器,它也会产生一个文件描述符。

一旦我有一个有效的文件描述符可供读取,我就可以将它提供给select() 系统调用以阻塞,直到有东西要读取。

为了处理“如果有人在我阻塞时从文件系统中删除管道怎么办”问题,我使用了pipe() 系统调用来获取一个未命名的管道(Python 版本只产生 2 个文件管道两端的描述符)。

我还将这个未命名管道的读取描述符提供给select() 调用,所以任何时候我想停止我的程序,我只需通过写入未命名管道的写入描述符来解除阻塞select(),而不管发生了什么使用命名管道。

【讨论】:

以上是关于如何解除阻塞已删除命名管道上的线程阻塞?的主要内容,如果未能解决你的问题,请参考以下文章

WCF 命名管道流

Java中的命名管道和多线程

非阻塞命名管道

从命名管道读取不会给出任何输出并无限期地阻塞代码

如何等待 n 秒以打开命名管道?

打开命名管道时,构造函数上的 FileInputStream 块