【问题标题】:What is an overlapped I/O alternative to WaitNamedPipe?WaitNamedPipe 的重叠 I/O 替代方案是什么?
【发布时间】:2012-03-26 19:40:48
【问题描述】:

WaitNamedPipe 函数允许管道客户端应用程序同步等待命名管道服务器上的可用连接。然后调用CreateFile 作为客户端打开管道。伪代码:

// loop works around race condition with WaitNamedPipe and CreateFile
HANDLE hPipe;
while (true) {
    if (WaitNamedPipe says connection is ready) {
        hPipe = CreateFile(...);
        if (hPipe ok or last error is NOT pipe busy) {
            break; // hPipe is valid or last error is set
        }
    } else {
        break; // WaitNamedPipe failed
    }
}

问题在于这些都是阻塞的、同步的调用。什么是异步执行此操作的好方法?例如,我似乎找不到使用重叠 I/O 来执行此操作的 API。例如,对于管道 serversConnectNamedPipe 函数提供了一个 lpOverlapped 参数,允许服务器异步等待客户端。然后管道服务器可以调用 WaitForMultipleObjects 并等待 I/O 操作完成,或任何其他事件发出信号(例如,一个事件信号通知线程取消挂起的 I/O 并终止)。

我能想到的唯一方法是在一个短暂的、有限超时的循环中调用WaitNamedPipe,并在超时时检查其他信号。或者,在循环调用CreateFile 中,检查其他信号,然后在短时间内调用Sleep(或WaitNamedPipe)。例如:

HANDLE hPipe;
while (true) {
    hPipe = CreateFile(...);
    if (hPipe not valid and pipe is busy) {
        // sleep 100 milliseconds; alternatively, call WaitNamedPipe with timeout
        Sleep(100);
        // TODO: check other signals here to see if we should abort I/O
    } else
        break;
}

但在我看来,这种方法臭不可闻。如果管道有一段时间不可用,线程会继续运行 - 占用 CPU、使用电源、需要内存页面保留在 RAM 中等。在我看来,依赖于 Sleep 或短超时的线程不会表现良好,是多线程编程草率的标志。

但是在这种情况下有什么替代方案?

【问题讨论】:

  • 一个明显的解决方案是将调用 WaitNamedPipe 放在一个单独的线程中。 IIRC,一些异步 IO 函数实际上在后台使用线程,所以这并不像听起来那么低效。
  • 告诉我们更多关于这段代码正在解决的竞争条件。 MS 文档示例客户端代码说首先调用 CreateFile,并且只有在 Create 失败且 Pipe Busy 时才调用 WaitNamedPipe。循环执行,等待适当的超时。根据我的经验,这一直有效。该错误仅在两个客户使用一个管道时发生,但这不是一场比赛。一个客户端连接,另一个等待并重试,这与您的 Sleep 示例发生的情况相同,但更简洁。
  • @Mark:在文档中。当 WaitNamedPipe 成功时,CreateFile 可能仍然失败,因为另一个线程先跳入。这是一个竞争条件:两个或更多线程正在竞相打开管道。将循环放入围绕竞争条件工作,因此在 OPs 代码中的注释。不过,这不是他要我们解决的问题。
  • 扩展答案。基本上,Poster 的CreateFile/Sleeploop 是解决这个问题的完美解决方案 - 无需进一步。
  • @Ben:不,这不是问题,但很痛苦。它使你的代码更难维护,因为模块化被破坏了。

标签: c++ c windows named-pipes


【解决方案1】:

WaitNamedPipe 完全没用,如果您指定超时并且没有服务器等待它,它将只使用所有 cpu。

只需像您正在做的那样用Sleep 一遍又一遍地调用CreateFile,然后将其移至您认为合适的其他线程。没有 API 替代方案。

WaitNamedPipe 提供的唯一“好处”是如果您想知道是否可以连接到命名管道,但您明确不想实际打开连接。太垃圾了。

如果你真的想彻底,你唯一的选择是

  • 确保打开命名管道的任何程序总是在命名管道连接后立即再次调用CreateNamedPipe
  • 让您的程序实际检查该程序是否正在运行。
  • 如果您真的不想建立额外的连接,仍然拨打CreateNamedPipe,当有人连接时,告诉他们离开,直到他们等待给定的时间,关闭管道。

【讨论】:

  • 我猜如果没有其他竞争,它只会使用所有 CPU?它至少应该在每个时钟滴答声中产生一次 CPU。这实际上是有道理的——这意味着底层协议没有提供任何排队连接的方法,这解释了为什么没有异步版本。 TCP 连接也是如此。如果服务器端没有监听,就没有办法说“哦,那好吧,你有空再给我回电话”,你只需要定期重试。
  • 只是被微软忽略了。当您使用超时调用WaitNamedPipe 时,您的一个cpu 将处于%100 直到它返回——它被称为活锁——其他东西将被允许运行,但该cpu 上根本不会有空闲周期。我怀疑他们会在一两年内解决这个问题。
  • 我最后只使用了有限的超时时间。
  • 请注意,如果您要连接的命名管道位于本地计算机上,则使用 0 超时将始终为您返回正确答案。如果您尝试确定远程主机上的命名管道是否可用(而不是连接),则只需要超时。
  • @fcrick:他所描述的行为(WaitNamedPipe 在等待时消耗额外的 CPU 周期)显然已经修复。我没有在 Windows 10 上观察到它。但我同意,这个 API 似乎没用。尤其是当它处于等待模式时,它没有提供任何停止它的方法。
【解决方案2】:

为什么服务器不能创建更多管道?如果很少见,您描述的场景中的性能损失不是问题。

即如果通常有足够的管道可以绕行,那么使用CreateFile/Sleep 而不是WaitForMultipleObjects 有什么关系?性能损失无关紧要。

我还不得不质疑客户端中是否需要重叠 IO。它一次与多少台服务器通信?如果答案小于 10,您可以合理地为每个连接创建一个线程。

基本上我是说我认为没有重叠WaitforNamedPipe 的原因是因为没有合理的用例需要它。

【讨论】:

  • 客户端无法控制服务器,需要做最坏的假设。 (在这种特殊情况下,服务器一次只处理一个客户端,但我认为即使一台服务器一次处理多个客户端,客户端仍然应该为完整的服务器做好准备。)
  • @JamesJohnston:你不是在写服务器代码吗?命名管道主要由一起编写的客户端和服务器使用。
  • @Ben 需要重叠 IO 版本的 WaitNamedPipe 的用例是客户端希望在等待时执行某些操作,但不能将等待放在自己的线程中。这不是一个不常见的用例!
  • @IanGoldby 您没有解释为什么不能将等待放在自己的线程中。线程并不那么昂贵。
【解决方案3】:

您可以在\\.\pipe\打开管道文件系统,然后使用DeviceIoControl发送FSCTL_PIPE_WAIT

【讨论】:

  • 我认为它也适用于远程管道,只需打开 \\machine\pipe 即可。
  • 您好,我无法使用此方法,DeviceIoControl 因 ERROR_INVALID_FUNCTION 而失败。任何想法我可能做错了什么?
猜你喜欢
  • 2023-03-19
  • 1970-01-01
  • 2021-11-13
  • 2018-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-08
  • 1970-01-01
相关资源
最近更新 更多