我们可以在Unices系统中用于异步I / O警报的设施,例如Linux上的epoll,BSD系统上的kqueue和Solaris / dev / poll或I / O端口,都允许用户指定一个指针与用户想要接收I / O警报的文件描述符相关联。
通常在this指针中,用户指定一个结构的指针,该结构将抽象文件描述符(例如“Stream”结构或类似的东西),并且每次新文件描述符时用户都将分配一个新结构是开放的。
E.g。 struct stream { int fd; int flags; callback_t on_read_fn; /* ... */ };
现在,我的问题是:如何安全地释放用户在多线程envinronment中分配的结构?
我问这个,因为epoll / kqueue / etc的性质: 您通常有一个线程从内核“下载”一个事件向量,包含具有一些I / O就绪的文件描述符,以及与该文件描述符相关联的用户指针。
现在,让我们考虑我有2个线程:T1下载这些事件并处理它们,例如调用stream->on_read_fn();
等,以及只运行用户代码,用户事件和类似内容的T2。
如果T2想要关闭文件描述符,只需close(stream->fd);
,T1就不再接收该fd的任何I / O警报,因此可以安全地释放stream
结构。
但是,如果T1线程已经在它正在处理的事件向量中下载了相同的文件描述符,那么它还没有处理该文件描述符呢?
如果在T2之前安排了T1,那就没问题,但是如果在T1之前调度T2,它将关闭文件描述符并释放stream
结构,所以线程T1,它将处理该文件描述符,将有一个用户关联指针,指向已经解除分配的结构!当然这会严重崩溃。
我的观点是T2将从不知道如果线程T1下载了针对该特定文件描述符的一些I / O警报,T2都无法预测IF T1是否会下载某些I / O警报一点都没有!
这非常棘手,它让我头晕目眩。有什么想法吗? 在这种情况下何时可以安全地释放用户指定的指针?
注意:我的一位朋友建议在调用close(2)
之前从epoll / kqueue队列中删除文件描述符。这是对的,这就是我现在所做的,但这不能解决问题,因为T2可以从epoll / kqueue队列中删除文件描述符,但这不能保证该文件的I / O事件描述符尚未从内核“下载”,并且将很快由线程T1处理。
答案 0 :(得分:1)
我有完全类似的问题,这就是为什么在新的Linux内核提案中,有人(不能记住这个名字)建议为FD实现DISABLED状态,这样你就可以跳过处理,如果它已被另一个线程解除分配。
就个人而言,我从多线程的epool调用转移到FD上的epool()的单个线程,然后将事件安排到多个线程。内部对象被引用计数,后来由垃圾收集器收集。诚实地工作,并且没有明显降低多线程epool解决方案......
*已编辑*
此外,我已经研究了通过创建受互斥锁保护的std :: set来从同一个线程关闭FD而不是处理epool的方法,并且只要FD需要关闭就由消费者线程填充。这也很有效。
答案 1 :(得分:1)
我在程序中解决了这个问题,方法是不释放结构,而是将其标记为“死”并将其添加到列表中,以便以后可以重用。这样,尽管指针可能已经被重用,但它始终保持有效。
答案 2 :(得分:0)
我宁愿避免在两个线程之间共享相同的数据结构。
过去,使用" one-shot"技巧,似乎在许多系统上都是可移植的。通过一次性行为,一旦发出事件信号,就会暂时"取出"对于队列,即没有其他线程会被通知任何fd变得可读或可写。
完成事件处理后,需要将其添加回epoll / kqueue(正如Linux doc所说,"重新启动" fd)。
在Linux上:
添加到epoll:epoll_ctl()/ EPOLL_CTL_ADD,标记EPOLLET | EPOLLONESHOT
使用相同的事件标记重新设定:epoll_ctl()/ EPOLL_CTL_MOD。
在带有kqueue的BSD / OSX上
添加到kqueue:EV_SET(... EV_ADD | EV_ONESHOT ...);
重新武装:EV_SET(...... EV_ADD | EV_ONESHOT ......);
在Solaris上
只需使用port_associate()来添加和重新武装。