我在 Windows 中使用 I/O 完成端口,我有一个名为“Stream”的对象,它类似于并抽象了一个 HANDLE(因此它可以是一个套接字、一个文件等)。
当我调用 Stream::read() 或 Stream::write()(因此,在文件的情况下是 ReadFile()/WriteFile(),在套接字的情况下是 WSARecv()/WSASend()),我分配了一个新的 OVERLAPPED 结构,以便发出将由其他线程在 IOCP 循环中完成的挂起 I/O 请求。
然后,当 OVERLAPPED 结构将被 IOCP 循环完成时,它将在那里被销毁。如果是这种情况, Stream::read() 或 Stream::write() 从 IOCP 循环中再次调用,它们将实例化新的 OVERLAPPED 结构,并且它将永远存在。
这工作得很好。但是现在我想通过添加 OVERLAPPED 对象的缓存来改进这一点:
当我的 Stream 对象进行大量读取或写入时,缓存 OVERLAPPED 结构绝对有意义。
但是现在出现了一个问题:当我取消分配一个 Stream 对象时,我必须取消分配缓存的 OVERLAPPED 结构,但是我怎么知道它们是否已经完成或仍在挂起,并且其中一个 IOCP 循环将在最近完成?所以,这里需要一个原子引用计数,但现在的问题是,如果我使用原子引用计数器,我必须为每个读取或写入操作增加该引用计数器,并在 OVERLAPPED 结构的每个 IOCP 循环完成时减少该引用计数器或流删除,在服务器中有很多操作,所以我最终会多次增加/减少很多原子计数器。
这会对多线程的并发性产生非常负面的影响吗?这是我唯一担心阻止我为每个 OVERLAPPED 结构放置这个原子引用计数器的问题。
我的担忧毫无根据吗?
我认为这是一个需要指出的重要主题,并且是一个关于 SO 的问题,看看其他人对此的想法以及使用 IOCP 缓存 OVERLAPPED 结构的方法,是值得的。
如果可能的话,我希望在不使用原子引用计数器的情况下找到一个聪明的解决方案。
最佳答案
假设您将数据缓冲区与 OVERLAPPED
结构捆绑为“每个操作”数据对象,然后将它们池化以避免过度分配/解除分配和堆碎片,这是一个好主意。
如果您只将此对象用于 I/O 操作,则不需要引用计数,只需从池中取出一个,使用它执行 WSASend/WSARecv,然后在完成后将其释放到池中IOCP 完成处理程序。
但是,如果您想变得更复杂,并允许将这些缓冲区传递给其他代码,那么您可能需要考虑对它们进行 ref 计数,如果这样更容易的话。我在我当前的框架中这样做,它允许我拥有事物的网络方面的通用代码,然后将读取完成的数据缓冲区传递给客户代码,他们可以用它们做他们想做的事情,当他们完成时他们发布他们回到游泳池。这目前使用引用计数,但我正在远离它作为一个小的性能调整。引用计数仍然存在,但在大多数情况下,它只会从 0 -> 1 然后再次变为 0,而不是在我的框架内的各个层进行操作(这是通过将缓冲区的所有权传递给用户来完成的)使用智能指针的代码)。
在大多数情况下,我预计引用计数不太可能是您最昂贵的操作(即使在 NUMA 硬件上,您的缓冲区正在从多个节点使用的情况下)。将这些东西放回池中所涉及的锁定更有可能成为你的瓶颈;我已经解决了那个问题,所以我正在转向下一个更高的水果;)
您还讨论了您的“每个连接”对象并将您的“每个操作”数据缓存在本地(这是我在将它们推回分配器之前所做的),而“每个操作”数据并不严格要求引用计数,“每个连接”数据至少需要一个原子可修改的“正在进行的操作数”计数,以便您可以判断何时可以释放 IT。同样,由于我的框架设计,这已成为客户代码可以保存引用以及事件 I/O 操作的正常引用计数。我还没有在通用框架中解决这个计数器的需要。
关于windows - 在 Windows 中使用 IOCP 时缓存 OVERLAPPED 结构,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/20681268/