如果在一个单独的线程中关闭文件描述符,select(2)会发生什么?

27

select(2)函数在其正在监听的文件描述符被其他线程关闭时会发生什么行为?

经过一些表面测试,它会立即返回。我猜测结果可能是:要么(a)它仍然继续等待数据,但如果您真正尝试从中读取数据,您将获得EBADF(可能存在潜在的竞态条件),要么(b)它会假装好像从未传入该文件描述符。 如果后一种情况是真的,那么在没有超时的情况下传入单个fd会在其关闭时导致死锁。


我认为这是不同的,尽管略微相关。另一个问题明确询问如何从另一个线程中退出select()(而pipe()是一个很好的答案),而我的问题更多地涉及到close()select()选定的套接字上的行为。在下面的答案中,你会看到答案是,“这取决于情况。” - Joe Shaw
我曾经遇到过的一次比较神秘的错误调试,结果发现是由于以下问题引起的:线程A在选择套接字#x时,线程B关闭了该套接字。不久之后,线程C创建了一个新的套接字,这也碰巧是套接字#x(因为网络堆栈选择重用数字x作为新套接字)。此时,线程A(仍在尝试使用套接字#x)当然开始在线程C的套接字上进行选择/读取/写入数据,尽管它们彼此完全没有逻辑连接。这是一个非常麻烦的问题。 - Jeremy Friesner
4个回答

24

经过进一步调查,看起来bothie和dwc都是正确的。

针对这个问题,bothie的回答可以概括为:这是未定义的行为。这并不意味着它一定是不可预测的,而是因不同的操作系统会有不同的处理方式。似乎像Solaris和HP-UX这样的操作系统在这种情况下从select(2)返回,但是根据linux-kernel邮件列表中的此帖子,Linux不会。

在linux-kernel邮件列表上的讨论基本上是,这是一个未定义的(破碎的)行为,不能依靠它。在Linux的情况下,调用close(2)关闭文件描述符相当于将其引用计数减小了。由于还有一个select(2)调用引用它,该文件描述符将保持打开状态并等待输入,直到select(2)返回。这基本上就是dwc的回答。您将在文件描述符上获得一个事件,然后它将被关闭。如果该文件描述符没有被重用,则尝试从中读取将导致EBADF(“Bad file descriptor”)错误。(这是MarkR在他的答案中提到的问题,尽管我认为使用适当的同步方法可以避免这个问题。)

所以非常感谢大家的帮助。


这是不可预测的。在一个线程中关闭文件描述符,而另一个线程正在使用select进行读取时,没有可预测的方法,因为调用close的线程无法知道另一个线程是否已经被阻塞在select中或即将被阻塞在select中。 - David Schwartz

7
我认为它的行为应该像已到达文件末尾一样,也就是说,它会返回文件描述符已准备好,但随后任何尝试读取它的操作都会返回“坏的文件描述符”。
话虽如此,这样做是非常不好的实践,因为你总是有可能存在竞争条件,即另一个具有相同编号的文件描述符可能会被另一个线程立即打开,然后选择线程将在错误的文件上等待。
一旦关闭文件,它的编号就变成可重用的,并且可能会被下一次调用open()、socket()等函数重用,即使是由另一个线程调用。因此,你真的需要避免这种情况。

我曾以为它也可能会返回为“ready”,但那并不完全正确:描述符实际上并不处于就绪状态——它已关闭。正如你所提到的,当你使用它时,它可能已被重新分配给其他内容。 - Joe Shaw
你可以通过在包含fd的数据结构上使用互斥锁来避免竞态条件。但是,只有在select()调用中定义了超时时限才能起作用。 - Joe Shaw

6
选择系统调用是一种等待文件描述符状态改变的方式,而程序没有其他事情可做。主要用于服务器应用程序,它们打开了一堆文件描述符,然后等待它们上面发生任何事情(接受新连接,读取请求或发送响应)。这些文件描述符将以非阻塞IO模式打开,以使服务器进程在任何时候都不会挂起系统调用。
此外,这意味着无需单独的线程,因为所有可以在线程中完成的工作也可以在选择调用之前完成。如果工作时间很长,那么可以使用超时参数timeout={0,0}来中断它,处理文件描述符,然后恢复工作。
现在,在另一个线程中关闭文件描述符。你为什么需要额外的线程,它为什么要关闭文件描述符?
POSIX标准没有提供任何提示,发生这种情况会发生什么,所以你所做的是未定义行为。请预计结果在不同操作系统甚至相同操作系统的版本之间会有很大的不同。
祝好,Bodo

1
我认为无论如何都会有未定义的行为,因为不可能消除文件描述符在选择操作之前被关闭并且另一个使用相同编号的文件描述符被打开的竞态条件。 - MarkR

3

你的问题有点令人困惑...

Select()应该在“有趣”的更改发生时返回。如果close()只是减少引用计数并且文件仍然在某个地方打开进行写入,那么没有理由让select()唤醒。

如果另一个线程对唯一打开的描述符执行了close(),那么情况会变得更加有趣,但我需要看到代码的简化版本才能确定是否真的出现了问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接