异步C ++客户端中关于CompletionQueue的困惑

时间:2020-03-08 12:47:41

标签: grpc

关于如何将CompletionQueue用于异步C ++客户端,我有些困惑。我的服务器是C#,所以这里的问题纯粹是客户端以异步方式向服务器发送请求。

作为参考,这是我设置客户端代码以发出异步请求的方式:

template<typename ResponseType, typename AsyncOpExecutor>
bool DoAsyncOp(const AsyncOpExecutor& op, const unsigned int deadlineMs, ResponseType& response)
{
    grpc::ClientContext ctx;
    grpc::CompletionQueue queue;

    const std::unique_ptr<grpc::ClientAsyncResponseReaderInterface<ResponseType>> asyncOpResponse = op(ctx, queue);

    grpc::Status status;
    int requestTag = 1;
    asyncOpResponse->Finish(&response, &status, (void*)requestTag);

    bool result = false;
    bool gotEvent = false;

    do
    {
        const std::chrono::time_point<std::chrono::system_clock> deadline = std::chrono::system_clock::now() + std::chrono::milliseconds(deadlineMs);

        void* got_tag;
        bool ok = false;
        const grpc::CompletionQueue::NextStatus nextStatus = queue.AsyncNext(&got_tag, &ok, deadline);

        switch (nextStatus)
        {
        case grpc::CompletionQueue::NextStatus::TIMEOUT:
            continue;

        case grpc::CompletionQueue::NextStatus::GOT_EVENT:
            assert(got_tag == (void*)requestTag);
                        // ok is always true even if I close the server while request is in progress.
            assert(ok);
            result = status.ok();
            gotEvent = true;
            break;

        // Given that I am creating a new CompletionQueue per request (not using a shared one), is this flag likely to occur?
        case grpc::CompletionQueue::NextStatus::SHUTDOWN:
            result = false;
            gotEvent = false;
            break;

        default:
            result = false;
            gotEvent = false;
            break;
        }
    } while (!gotEvent);

    return result;
}

我的第一个困惑是设置CompletionQueue的最佳方法。 This answer似乎建议可以在请求中使用单个CompletionQueue。如果多个线程使用同一队列发出请求,这将如何表现。假设我将上面的代码更改为使用共享队列,而不是为每个请求创建一个新队列。

  • 一个线程如何知道它收到的响应是针对它的,而不是针对另一个线程的?

  • 我是否需要为每个线程分配一个唯一的标签,并在每个线程上检查从队列接收的标签是否与我最初发送的标签匹配?

  • 如果线程A收到了用于线程B的标签,这是否意味着线程B以后可以查询其标签,还是该标签丢失了,因为线程A首先看到了它?

  • 实际上是否存在每个请求使用新队列而不是共享的主要问题?

我的第二个困惑是关于grpc::CompletionQueue::NextStatus::SHUTDOWN的结果。如果我对每个请求使用一个新的队列,而不在队列上显式调用shutdown,是否有可能发生这种结果?如果是,将触发什么?我进行的一项测试是在请求进行过程中关闭服务器,但是我没有得到grpc::CompletionQueue::NextStatus::GOT_EVENT结果,而是将状态设置为UNAVAILABLE。{p>

我最后的困惑是围绕ok标志。我已经读过this answer,但是仍然不太清楚。给定上面发布的用例和代码,如果我从队列中得到的结果为grpc::CompletionQueue::NextStatus::GOT_EVENT,则ok标志可以为false,如果是,则将导致它为false?同样,这完全是围绕客户端,而不是服务器上CompletionQueue的处理方式。

1 个答案:

答案 0 :(得分:2)

  1. 使用从完成队列返回的标签来了解哪个完成队列操作启动了此操作。您必须确保使用同一标签的同时没有进行两个CQ操作。
  2. 每个飞行中的CQ操作同时需要一个不同的标签。无论这些操作是从同一线程还是从不同线程启动的,都适用。
  3. 每个标签仅从Next函数中出现一次。如果您有多个线程在同一个CompletionQueue上调用Next,则它们都应该能够处理(或传递)注册到该CQ的任何标签。
  4. 您可以为每个RPC使用一个新队列,就像sync API实际上在内部一样。那样效率较低,并且在这种情况下基本上会转移到sync API,因为您将无法同时进行许多未完成的操作。

您需要在某个时候调用Shutdown,然后清空队列。这是API的标准。否则,特别是对于服务器CQ,可能会发生泄漏。通过消耗,我的意思是调用Next,直到它返回false(或调用AsyncNext,直到它返回SHUTDOWN)。

对于流调用,服务器端调用等,GOT_EVENT的

ok可以为false。如果仅查看客户端一元调用(如您的示例),则不会为false。错误的ok本质上意味着RPC的这一面已损坏。有关此文档的信息位于CQ的头文件中。