首页 > 解决方案 > C++:处理多个输入源、可输出性和异常标志,以及来自多个线程的计时器

问题描述

我已经编写单线程事件驱动应用程序 30 年了。

一般来说,这是通过 1) 将所有 IO 设置为非阻塞的,然后 2) 编写一个包装器select()poll()或类似的东西来维护一个计时器事件队列和文件描述符数组,以便监视可读性、可写性和异常。除其他外,X Windows Toolkit Intrinsics (Xt) 和 XView 库提供了这样的包装,路透社 RTR 和其他几个来源也是如此。

结果是一个单线程应用程序,它仍然可以监视任意数量的文件句柄(例如 TCP 会话、来自窗口环境的用户事件等)以及大量计时器。如果一个套接字变得可读(即存在更多数据),它就会被读取。如果它变为可写(即,写缓存被完全或部分清除),则可以写入更多数据,依此类推。

现在我正在多线程环境中编写相同类型的通用软件。

显然,一种方法是使所有 I/O 阻塞,并使用单独的线程来读取和写入每个 TCP 连接。同样,线程可能在被计时器唤醒之前一直处于睡眠状态(我认为可能,但如何?)。然而,设置所有这些线程并确保它们使用锁访问公共变量或在所有情况下都有效的原子操作,程序员的工作量似乎比老式方法更糟糕。

另一个极端是维护单选,这可能是一个瓶颈,但对我来说是最熟悉的,并且允许对旧代码进行最多的重用。这是一个示例,说明为什么它不能在多线程世界中按原样使用。假设我想要一个计时器在 1 秒内关闭,但select()为下一个计时器事件阻塞 2 秒,并且没有其他文件活动会导致select()返回。我的 1 秒计时器最早会在 2 秒内关闭,这就是我记录收到请求的时间。(如果我在处理消息时天真地遵守了 1 秒的回调时间,那么它会在 3 秒后关闭,而不是请求的 1 秒。)

select()作为一种解决方法,我可以创建一个管道并注册一个端点,以使用任何文件描述符来监视传入数据。主线程以外的线程只需将其注册或删除计时器或输入源的请求写入管道。传入的数据会唤醒 select(),然后主线程可以(几乎)立即将其添加到其计时器队列中,或者从其被监视的数组中添加/删除文件描述符等。

这也有一些棘手的问题,例如注册一个计时器变得异步。如果您传入对需要计时器的对象的引用,然后想在计时器关闭之前删除此对象,您不知道是否可以安全地释放它,因为计时器可能尚未注册,并且取消注册同样是异步的。假设我对所有这些都有可接受的解决方案(例如根本不删除此类对象)。

但我确信在这两个极端之间使用了一些标准方法。它们可能是什么?select()也许就像为每个需要计时器或 I/O 事件的线程设置一个基于 - 的顶部循环一样简单?

标签: c++multithreadingselectevent-handling

解决方案


推荐阅读