对此的解决方案相当简单,如果实施起来有点烦人。它依赖于一个fcntl
被调用的F_SETSIG
, 来指定用于传达 FD 状态更改的信号,以及一个fcntl
被调用F_SETOWN_EX
来指定应该将信号传递到哪个线程。
当应用程序启动时,它会产生一个单独的监控线程。该线程用于接收 FD 生成的信号。
在我们的特定用例中,监视线程必须在第一次创建受监视的 FD 时隐式启动,并在没有显式连接的情况下销毁。这是因为我们正在模拟一个 FreeBSD API (kqueue),它没有显式的 init 和 deinit 函数。
监控线程:
- 监听我们传递给的信号
F_SETSIG
。
- 获取其线程 ID并将其存储在全局中。
- 使用 .通知应用程序监视线程已启动(并且全局已填充)
pthread_cond_broadcast
。
- 调用
pthread_detach
以确保正确清理它,而无需另一个线程需要执行显式pthread_join
.
- 呼叫
sigwaitinfo
等待信号的传递。
应用程序线程:
- 用于
pthread_once
在第一次创建 FD 时启动监控线程,然后等待监控线程完全启动。
- 用于
F_SETSIG
指定 FD 打开/关闭时发送的信号,并将F_SETOWN_EX
这些信号定向到监控线程。
当被监控的 FD 关闭sigwaitinfo
时,监控线程中的调用返回。在我们的例子中,我们使用管道来表示 kqueue,因此我们需要将我们接收到的信号的 FD映射到与我们需要释放的资源(kqueues)相关联的那个。一旦这个映射完成,我们可以(更多信息见下文)清理与 FD 对关联的资源,并sigwaitinfo
再次调用以等待更多信号。
该策略的其他关键部分之一是与 FD 关联的资源是引用计数的。这是因为信号不是同步传递的,所以可以关闭一个 FD,并且可以在指示原始 FD 已关闭、传递和操作的信号之前创建一个具有相同编号的新 FD。这显然会导致活动资源被释放的大问题。
为了解决这个问题,我们维护了一个互斥同步 FD 到资源映射数组。此数组中的每个元素都包含特定 FD 的引用计数。
如果在创建新管道/资源对时重用 FD 之前未传递信号,则该特定 FD 的引用计数将 > 0。发生这种情况时,我们立即释放资源,并重新初始化它,增加引用数数。当指示 FD 已关闭的信号被传递时,引用计数递减(但不为零),并且不释放资源。
或者,如果在重用 FD 之前传递了信号,那么监控线程会将引用计数减为零,并立即释放相关资源。
如果此描述有点令人困惑,您可以使用上面的任何链接查看我们在现实世界中的实现。
注意:我们的实现与上面描述的不完全一样(值得注意的是,我们在创建新的 FD/资源映射时不检查 FD 的引用计数)。我认为这是因为我们依赖于这样一个事实,即关闭一个管道并不一定会导致另一端被关闭,因此开放端的 FD 不能立即用于重用。不幸的是,编写代码的开发人员无法查询。