【问题标题】:Caching OVERLAPPED structures when using IOCPs in Windows在 Windows 中使用 IOCP 时缓存 OVERLAPPED 结构
【发布时间】:2013-12-19 12:00:49
【问题描述】:

我在 Windows 中使用 I/O 完成端口,我有一个名为“Stream”的对象,它类似于并抽象了一个 HANDLE(因此它可以是套接字、文件等)。

当我调用 Stream::read() 或 Stream::write() 时(因此,对于文件,ReadFile()/WriteFile() 和对于套接字的 WSARecv()/WSASend()),我分配了一个新的 OVERLAPPED 结构,以便发出一个挂起的 I/O 请求,该请求将由其他线程在 IOCP 循环中完成。

然后,当 IOCP 循环完成 OVERLAPPED 结构时,它将在那里被销毁。如果是这种情况,则从 IOCP 循环再次调用 Stream::read() 或 Stream::write(),它们将实例化新的 OVERLAPPED 结构,并且它将永远存在。

这很好用。但现在我想通过添加 OVERLAPPED 对象的缓存来改进这一点: 当我的 Stream 对象进行大量读取或写入时,缓存 OVERLAPPED 结构绝对有意义。

但是现在出现了一个问题:当我释放 Stream 对象时,我必须释放缓存的 OVERLAPPED 结构,但是我如何知道它们是否已经完成或仍在等待中,并且最近 IOCP 循环之一将完成呢?所以,这里需要一个 atomic 引用计数,但现在的问题是,如果我使用原子引用计数器,我必须为每个读取或写入操作增加该引用计数器,并减少每个 IOCP OVERLAPPED 结构或流删除的循环完成,这在服务器中是 很多 操作,所以我将通过增加/减少 很多 原子计数器来结束em>很多次。

这会对多线程的并发性产生非常负面的影响吗?这是我唯一担心的问题,阻止我为每个 OVERLAPPED 结构放置这个原子引用计数器。

我的担忧毫无根据吗?

我认为这是一个需要指出的重要话题,关于 SO 的问题,看看其他人对此的想法以及使用 IOCP 缓存 OVERLAPPED 结构的方法,是值得的。 如果可能的话,我希望在不使用原子引用计数器的情况下找到一个聪明的解决方案。

【问题讨论】:

  • 当操作需要几毫秒时,您会担心纳秒。这非常符合“万恶之源”的口头禅。
  • '那么,当 OVERLAPPED 结构将被 IOCP 循环完成时,它将在那里被销毁。' - 你为什么要毁掉它?它应该是流类的成员,或者是在 WSA 调用中缓存和使用的某个其他“IOCPbuffer”类的成员,其数据在 IOCP 完成后应用于其流,然后再释放回其池。
  • Martin,是的,这正是我想要实现的,我想知道原子引用计数器是否可以用于那些“IOCPbuffer”结构。汉斯,问题不在于纳秒或毫秒,而在于锁定,一堆LOCKed指令会影响线程并发,因为当一个内存区域(例如原子计数器)被原子读/写时,其他内核必须等待完成原子事务。
  • AFAIK,你不需要任何原子计数器。如果您正在缓存/池化这些实例,则永远不会销毁它们,只需重新池化它们即可。池本身需要一个锁,但处理锁所花费的时间肯定会因避免不断创建/销毁类实例而被淹没。
  • 哦.. 等一下,您是否正在向多个客户端发送相同的缓冲区?这就是你需要 refCount 的原因吗?

标签: windows multithreading sockets atomic iocp


【解决方案1】:

假设您将数据缓冲区与 OVERLAPPED 结构捆绑为“每个操作”数据对象,然后将它们池化以避免过度分配/释放和堆碎片是个好主意。

如果您只将此对象用于 I/O 操作,则不需要引用计数,只需从池中拉出一个,使用它执行 WSASend/WSARecv,然后在完成后将其释放到池中在 IOCP 完成处理程序中使用它。

但是,如果您想变得更复杂一些,并允许将这些缓冲区传递给其他代码,那么您可能需要考虑对它们进行 ref 计数,如果这样更容易的话。我在我当前的框架中这样做,它允许我为事物的网络方面提供通用代码,然后将数据缓冲区从读取完成传递到客户代码,他们可以用它们做他们想做的事情,当他们完成时,他们会发布他们回到游泳池。这目前使用参考计数,但我正在远离它作为一个小的性能调整。引用计数仍然存在,但在大多数情况下,它只会从 0 -> 1 然后再次变为 0,而不是在我的框架内的各个层进行操作(这是通过将缓冲区的所有权传递给用户来完成的使用智能指针的代码)。

在大多数情况下,我预计引用计数不太可能是您最昂贵的操作(即使在 NUMA 硬件上,在从多个节点使用缓冲区的情况下也是如此)。将这些东西放回池中所涉及的锁定更有可能成为您的瓶颈;我已经解决了这个问题,所以我正在转向下一个更高的水果;)

您还谈论了您的“每个连接”对象并在本地缓存您的“每个操作”数据(这是我在将它们推回分配器之前所做的),而“每个”并不严格要求引用计数操作”数据,“每个连接”数据至少需要一个原子可修改的“正在进行的操作数”计数,以便您知道何时可以释放 IT。同样,由于我的框架设计,这已成为客户代码可以保存引用以及活动 I/O 操作的正常引用计数。我还没有在通用框架中解决对这个计数器的需求。

【讨论】:

  • 谢谢伦。好吧,我这里有一个通用框架,所以我不需要向用户公开 io 请求数据包,而是想隐藏它们。对于 (A) 的正常设计,采用 io req.来自池的数据包和 (B) 从 iocp 处理程序线程将其放回池中确实会带来新的瓶颈:池的锁定。我的目标是保持相同的 io req。数据包,直到连接的生命周期,所以直到 Stream 对象的生命周期。现在我唯一担心的是何时我必须释放io req。数据包,在 Stream 的 dtor 内部,还是在 iocp 处理程序内部?
  • 如果数据包处于待处理状态,我将在 iocp 处理程序上执行此操作,如果它不再待处理且不再使用,我必须从 Stream 的 dtor 中释放它,否则它会泄漏数据包.现在的问题是我不知道我是否需要一些原子计数器(甚至是布尔值),它只是告诉我 atomically 数据包不再待处理,没有其他线程有任何类型的所有权,所以我可以从 Stream 的 dtor 中删除它。有人说我这里不需要原子性,但这是真的吗?我有点怀疑。
  • 假设您在对其发出的所有操作完成之前从未解除分配流,那么您不需要在每个操作数据中使用计数器,但您可能需要在流中使用计数器来跟踪正在进行中的操作。
  • 在所有待处理的操作完成之前,我确实从不释放流。因为我每次只做 1 次操作,所以我有 2 个标志:挂起的读取和挂起的写,所以当我有 00== 没有挂起的操作,11== 写和读都挂起,01== 挂起,等等.我在调用 ReadFile() 之前设置了标志,并在 iocp 处理程序中将其设置为关闭。在这里我不需要原子标志,也不需要原子计数器,因为我只执行 1 个操作,对吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-04
  • 1970-01-01
  • 2021-10-03
  • 1970-01-01
相关资源
最近更新 更多