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