POSIX 和其他标准对多个线程同时在单个套接字或管道句柄上执行poll()
或调用的情况有何看法?select()
如果有任何数据到达,是只有一个等待线程被唤醒还是所有等待线程都被唤醒?
POSIX 和其他标准对多个线程同时在单个套接字或管道句柄上执行poll()
或调用的情况有何看法?select()
如果有任何数据到达,是只有一个等待线程被唤醒还是所有等待线程都被唤醒?
有趣的问题……我通读了当前的POSIX并没有找到具体的答案,即没有关于并发调用的规范。所以我会解释为什么我认为标准意味着所有人都会醒来。
/文本的相关部分是:select
pselect
成功完成后,pselect() 或 select() 函数应修改 readfds、writefds 和 errorfds 参数指向的对象,以分别指示哪些文件描述符准备好读取、准备好写入或有错误条件未决, [...]
然后
当对带有 O_NONBLOCK 清除的输入函数的调用不会阻塞时,无论该函数是否会成功传输数据,都应认为描述符已准备好读取。(该函数可能返回数据、文件结束指示或指示它被阻塞的错误以外的错误,并且在这些情况下,描述符应被视为已准备好读取。)
简而言之(仅限阅读案例),我们可以将其理解为:
select
不阻塞这意味着下一次调用输入函数O_NONBLOCK
不会返回错误errno==EWOULDBLOCK
。[请注意,“下一个”是我对上述内容的解释。]
如果承认这种解释,那么两个并发select
调用都可以返回相同的 FD 可读。事实上,即使它们不是并发的,但第一个线程调用select
时某些 FD 是可读的,然后,例如,两者之间的read
第二个线程调用select
可以返回 FD 作为第二个线程的可读。
现在问题的“唤醒”部分的相关部分是:
如果选定的描述符都没有为请求的操作做好准备,则 pselect() 或 select() 函数将阻塞,直到至少有一个请求的操作准备好,直到发生超时,或直到被信号中断。
在这里,上述解释清楚地表明同时等待的呼叫将全部返回。
由于这个问题,我刚刚发现了一个错误:我有两个线程在同一个套接字上进行选择,并且当 fd 作为 isset() 返回时将调用接受。事实上,两个线程的 select 都返回,两个线程中的 fd isset(),两个线程都调用 accept(),一个获胜,另一个阻塞等待另一个连接进入。
所以实际上 select 将在它为同一个 fd 阻塞的所有线程中返回。
它们都应该唤醒,都返回相同的结果值,并且都对 FD 集执行相同的操作。他们都在问同样的问题,所以他们都应该得到同样的答案。
根据此处引用的 POSIX 文档以及我select()
仅 25 年的经验,应该做的是返回当时可读、可写等的 FD 的数量。因此,所有并发select()
调用不都返回相同的东西是完全不正确的。
该select()
函数无法预测未来,即哪个线程实际上将进行读取或写入,因此哪个线程将成功。他们争辩。这是一个雷鸣般的羊群问题。
为了避免系统过载,我在内核上做了一些工作来为 Linux epoll 实现 EPOLLEXCLUSIVE。此标志设置为资源将确保只有一个侦听器将接收事件,即使在多个线程或进程正在通过 epoll()(轮询/选择的 Linux 版本)侦听给定文件描述符的情况下也是如此。这是一个非常有用的功能。例如,Enduro/X 中间件是基于多进程的中间件,其中几个负载平衡的可执行文件通过使用 epoll 监视同一组文件描述符(队列)。因此,当事件在没有 EPOLLEXCLUSIVE 的情况下到达时,许多进程会得到错误的唤醒(其中一些第一次唤醒确实已经从 FD 中删除了事件),而其他进程则得到空通知。如果说有 500 个二进制文件等待事件,那么空处理会花费 CPU 处理时间......