【问题标题】:IOCP loop termination may cause memory leaks? How to close IOCP loop gracefullyIOCP循环终止可能导致内存泄漏?如何优雅地关闭 IOCP 循环
【发布时间】:2013-03-21 12:36:49
【问题描述】:

我有经典的 IOCP 回调,它以这种方式使 i/o 挂起的请求出列、处理它们并释放它们:

struct MyIoRequest { OVERLAPPED o; /* ... other params ... */ };
bool is_iocp_active = true;

DWORD WINAPI WorkerProc(LPVOID lpParam)
{
    ULONG_PTR dwKey;
    DWORD dwTrans;
    LPOVERLAPPED io_req;
    while(is_iocp_active)
    {
        GetQueuedCompletionStatus((HANDLE)lpParam, &dwTrans, &dwKey, (LPOVERLAPPED*)&io_req, WSA_INFINITE); 
       // NOTE, i could use GetQueuedCompletionStatusEx() here ^ and set it in the 
       // alertable state TRUE, so i can wake up the thread with an ACP request from another thread!

        printf("dequeued an i/o request\n");
        // [ process i/o request ]
        ...

        // [ destroy request ]
        destroy_request(io_req);
    }
    // [ clean up some stuff ] 
    return 0;
}

然后,在代码中我会有某处:

MyIoRequest * io_req = allocate_request(...params...);
ReadFile(..., (OVERLAPPED*)io_req);

这简直完美。

现在我的问题是:如果我想立即关闭 IOCP 队列而不导致泄漏,该怎么办? (例如,应用程序必须退出) 我的意思是:如果我将 is_iocp_active 设置为 'false',下一次 GetQueuedCompletionStatus() 将出列一个新的 i/o 请求,这将是最后一个 i/o 请求:它将返回,导致线程退出,当线程退出时根据 MSDN,它的所有未决 i/o 请求都被系统简单地取消了。

但是我在调​​用 ReadFile() 时实例化的“MyIoRequest”类型的结构根本不会被销毁:系统已取消挂起的 i/o 请求,但我必须手动销毁我拥有的那些结构 已创建,否则当我停止循环时,我将泄漏所有待处理的 i/o 请求!

那么,我该怎么做呢?仅将该变量设置为 false 来停止 IOCP 循环是错误的吗?请注意,即使我使用 APC 请求停止可警报线程,也会发生这种情况。

我想到的解决方案是将每个“MyIoRequest”结构添加到队列/列表中,然后在 GetQueuedCompletionStatusEx 返回时将它们出列,但这不应该造成一些瓶颈,因为此类 MyIoRequest 的入队/出队过程结构必须互锁吗?也许我误解了如何使用 IOCP 循环。有人可以对这个话题有所了解吗?

【问题讨论】:

  • 提防过早停止。

标签: windows multithreading io iocp


【解决方案1】:

我通常关闭 IOCP 线程的方式是发布我自己的“请立即关闭”完成。这样您就可以干净地关闭并处理所有待处理的完成,然后关闭线程。

执行此操作的方法是调用 PostQueuedCompletionStatus(),其中 num 字节、完成键和 pOverlapped 为 0。这将意味着完成键是一个唯一值(您不会拥有具有零句柄/完成键的有效文件或套接字)。

第一步是关闭完成源,因此关闭或中止您的套接字连接、关闭文件等。一旦所有这些都关闭,您将无法再生成任何完成数据包,因此您可以发布特殊的 '0 '完成;为您为 IOCP 提供服务的每个线程发布一个。一旦线程获得“0”完成键,它就会退出。

【讨论】:

  • 问题在于确定 CancelIOEx 操作是否完成以及所有未完成的“已取消”完成消息是否已处理。未完成的 WSAblah 将发出大量完成消息,并且可能需要使用服务器范围的原子 refCount 来确定在发出“空消息”毒丸之前已处理所有这些完成。一种可能的出路是池线程在终止之前将收到的药丸重新排队,以便其他线程得到它。这意味着任意数量的池线程只需要一个药丸。
  • 根本不需要使用 CancelIOEx,恕我直言。当底层套接字和句柄关闭时,您只需让操作完成并出现错误。在发送 0 完成(每个线程一个)之前,您不必等待处理完成,只需先关闭套接字/句柄,然后发出 0 字节完成。关闭将触发未完成的操作的完成,线程将全部处理它们然后关闭。 10 多年来,我一直在这样做,为各种客户设计了一套非常多样化的服务器,而且它确实有效。
  • 嗨,Len,很高兴在这里见到你。好吧,这正是我所做的,我向所有线程发送一个带有“退出”操作的数据包。这里的问题是我有其他完成数据包排队退出数据包之后,所以线程退出,没有人将其他数据包出列并且它会泄漏。因此,在您看来,IOCP 的正确行为是在发送“退出”数据包之后拒绝数据包排队?你认为我必须更好地同步它,所以在“退出”数据包之后没有其他完成请求发送到完成端口?
  • 这个问题是,我也将 IOCP 线程池用于异步任务,例如 ayncClose(handle);这将在线程池的一个线程上调用 CloseHandle,然后调用回调 [例如关闭()]。现在,问题是套接字的 CloseHandle() 已排队,但尚未执行,因此此时套接字可以接收数据,因此实例化了其他 OVERLAPPED 结构!因此,如果我为所有套接字调用 asyncClose(),然后立即将“退出”数据包发送到所有线程,我可能会有一些泄漏!
  • 一个解决方案是等到 all 句柄打开和 发送之后调用 all onClosed()退出'数据包,但它很困难:如何?我认为一个有趣的解决方案是发送一个“唤醒退出”数据包,它只是让 GetQueuedCompletionStatus() 唤醒,设置一个标志“IOCP 已完成”而不退出循环[我的意思是离开 is_iocp_active== true] 并开始调用 GQCS() 并将其 'dwMilliseconds' 参数设置为 0,如果它返回请求则简单地销毁请求以避免泄漏,最后当 GQCS 返回队列为空时,线程返回 0;跨度>
【解决方案2】:

如果您要终止应用,并且没有最重要的理由不这样做(例如关闭数据库连接、进程间共享内存问题),请调用 ExitProcess(0)。

如果失败,请为所有套接字句柄调用 CancelIO(),并在它们进入时处理所有已取消的完成。

先试试 ExitProcess()!

【讨论】:

  • 嗯,使用 ExitProcess() 我几乎可以肯定内存将被释放,但我已经问过,当我只想停止循环时。那是因为在 C++ 中我有一个抽象 IO 完成端口的对象,所以,当我在该对象上调用 terminate() 时,我想停止它的所有 IO 完成端口循环。使用 CancelIO() 我取消了 I/O 请求(例如,该请求将从列表中删除?)但我不确定是否可以避免内存泄漏,因为 io_req 对象仍然存在......跨度>
  • 它们会在 CancelIO() 之后在大量完成消息中返回,不是吗?
  • 另外 - 不需要标志。在 IOCP 队列上发布“立即退出”毒丸消息。
  • 是的,我通常在 IOCP 队列上发布“立即退出”消息,但是队列很长怎么办?如果我的应用程序必须立即关闭 IOCP,这将需要一段时间,您可以立即使用该标志。
  • 嗯,是的,取决于完成队列中有多少条目。如果没有,因为服务器不忙,您的池线程将看不到该标志。
猜你喜欢
  • 2010-09-28
  • 1970-01-01
  • 1970-01-01
  • 2014-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-10
相关资源
最近更新 更多