【问题标题】:C++ async OutputDebugStringC++ 异步输出调试字符串
【发布时间】:2013-01-26 20:09:10
【问题描述】:

使用 c++11 中的新 std::async,我想我可能会尝试实现 OutputDebugString 的异步版本,以摆脱由于我通常大量打印每个小细节而导致的一些性能下降通过通常的 OutputDebugString 函数。

所以这是我原来的同步 OutputDebugString 实现(有效):

static void OutputDebugStringN(const char *format, ...)
{
    char outstring[256];
    memset(outstring, 0, sizeof(outstring));

    try
    {
        va_list args = {0};
        va_start(args, format); //args = (va_list) (&format+1);

        vsprintf_s(outstring, format, args);

        va_end(args);

        OutputDebugString(outstring);
    }
    catch (...) //most likely reference val arg error (va_list doesn't support ref args)
    {
        OutputDebugString("[OutputDebugStringN] Something went wrong\n");
    }
}

以下是我非常天真地尝试异步版本(不起作用):

static void OutputDebugStringN(const char *format, ...)
{
    auto future = std::async([]{
        char outstring[256];
        memset(outstring, 0, sizeof(outstring));
        try
        {
            va_list args = {0};
            va_start(args, format); //args = (va_list) (&format+1);

            vsprintf_s(outstring, format, args);

            va_end(args);

            OutputDebugString(outstring);
        }
        catch (...) //most likely reference val arg error (va_list doesn't support ref args)
        {
            OutputDebugString("[OutputDebugStringN] Something went wrong\n");
        }
    }); 
}

由于上述方法不起作用,我现在开始认为异步调用 OutputDebugStringN 可能比尝试在函数本身内部启动异步作业更好(这有效,但是麻烦):

auto dstring = std::async([]{ OutputDebugStringN("[NovelScript::ParseTokens] searched bookmark: \"%s\" does not exist\n", bookmark.c_str());} );

所以我想问两个问题:

  1. 我应该如何实现 OutputDebugString 的异步版本?
  2. 我是否应该尝试实现 OutputDebugString 的异步版本?

也欢迎对上述代码和任何其他 cmets 提出批评。

【问题讨论】:

  • 您是否尝试使用 std::async?我的问题当然是它只在后来的 c++ 中,所以它并不总是可用的。您可以通过构建自己的生产者-消费者队列和自己的线程来实际发送消息来实现异步。您在上面使用 async 是不正确的。它不会给你你想要的行为。
  • @thang 这只是我的一个小型个人项目,所以我并不介意它只是 c++11 的一部分。虽然我对异步实现并不是很熟悉(正如您已经看到的那样) - 如果有机会,您将如何实现它?
  • 我会写一个很长的回复..需要比评论中分配的更多空间
  • @thang 谢谢大家的帮助。非常感谢。
  • outstring 更改为char outstring[256] = { };,你不需要那个memset 废话。 ;-]

标签: c++ asynchronous c++11


【解决方案1】:

我认为你应该为你的消息设置一个队列,而不是在每次调用你的函数时启动一个线程,这样你的消息就会以正确的顺序输出干净。

所以你的功能,例如OutputDebugStringN(const char *format, ... ) 将创建消息字符串,然后将单独的打印输出线程从中读取的字符串排队。该线程将调用OutputDebugString

这里有一个例子——虽然不完整,没有错误处理和 print_from_queue 应该被修改为运行直到一些终止条件并且对 CPU 更友好一点。

std::mutex g_m;
std::deque<std::string> que;
std::atomic<bool> endcond = false;

void queue(std::string msg)
{
  std::lock_guard<mutex> _(g_m);
  que.push_back(msg);
}

void print_from_queue()
{
  while ( !endcond )
  {
    if ( que.size() )
    {
      std::lock_guard<mutex> _(g_m);
      std::string msg = que.front();
      que.pop_front();
      OutputDebugStringA(msg.c_str());
    }
  }
}

int debugf( const char *format,... )
{
  std::vector<char> line(256);
  va_list args;
  va_start( args, format );
  int len = vsprintf_s( &line[0], line.size(), format, args );
  va_end( args );
  queue( &line[0] );
  return len;
}

int _tmain(int argc, _TCHAR* argv[])
{
  auto thr = std::async( std::launch::async, print_from_queue );
  debugf("message1");
  debugf("message2");
...

【讨论】:

  • +1 当序列化的日志不利于性能分析时,我已经多次实现了这种精确的算法来调试日志记录。我非常推荐它。
  • 感谢您的回复;你能告诉我一个可能的实现吗?我对 async 的使用缺乏了解,这使我无法自己尝试这样做。
  • @dk123 你知道什么是信号量和临界区吗?如果是这样,那么您只需要一个队列和一个编写器线程来监视信号量以获取到达的消息。或互斥体和事件等。有几种方法可以做到。
  • @WhozCraig 如果可能的话,我更喜欢使用 std::async 的实现,以供个人学习和参考。有什么想法吗?
  • @dk123 老实说,它并不完全适合这个目的。最适合这种情况的是一个长时间运行的消费者线程,它从一个或多个生产者那里获取输入,将数据流序列化为 OutputDebugString。
【解决方案2】:

在我看来,调试应该是同步的,而不是异步的。如果调试器在异常发生几秒钟后给你异常,你会感激吗?在您的程序崩溃后(因为您将文件写入 asnyc),您是否希望有一个包含程序状态过时数据的日志文件?

无论如何,您继续将调试输出设置为异步的。它解决了什么目的,除了将数据转储到调试窗口之外,您知道它不是最近的,它不反映您/用户或程序的操作,都是陈旧!你绝对不能依赖它。

【讨论】:

  • 我同意,因此我的第二个问题是这样的实现是否有用。但是经过一番思考,我有点得出结论,如果我首先记录所有重新分级用户操作的信息等,那么日志和实际事件之间的时间延迟并不重要(无论如何我通常都会这样做)供将来参考)-在这种情况下,我需要做的就是实现一个不会干扰实际程序逻辑本身的日志记录类,以避免性能损失(因此是当前问题)。对此有什么想法吗?
  • 是的,简单的想法 - OutputDebugString 很可能比“延迟”它的过程运行得更快。此外,您需要确保您的程序在调试器下运行并采取相应措施。发送不会被处理以获得逻辑结果的异步消息将是浪费的。 IsDebuggerPresent 听起来很熟悉?函数指针、动态多态——你需要“禁用”异步消息传递!
  • 实际上这取决于应用程序。如果您正在运行高速视频处理管道,您确实需要异步调试消息,因为停止执行 OutputDebugString 会阻塞管道,这可能会破坏其他地方的代码行为。并且调试不必是实时同步的。有时您希望它按顺序排列。有时你不在乎。有时你只有一堆日志文件。有很多限制,这实际上取决于您的应用程序。
  • 知道一个简单的事实——OutputDebugString是在kernel32.dll中实现的,几乎是无缝调用(我没说是内核调用)。但是任何异步消息肯定需要很多线程/任务之间的“消息传递”,很少或更多的 CTOR 和 DTOR 调用。在性能方面,ODS 将胜过异步(不包括实际的 ODS 调用!)。
  • 实际上仅仅因为它在 kernel32 中并不意味着它很快。如果有人想接收消息,它实际上会停止 OutputDebugString。您可以通过测量运行调试视图的 OutputDebugString 所需的时间来证明这一点。异步消息不需要消息传递。简单地向队列中添加一个元素是最少的工作......您也可以自己衡量这一点。详情请看这篇文章:softwareverify.com/blog/?p=261。如果没有人沉迷于它的消息,OutputDebugstring 很快,但在这种情况下它是无用的。
【解决方案3】:

对上述代码和 C++ 异步函数的批评::p

std::async 的返回值是 std::future 类型的对象。由 std::async 创建的 std::future 的析构函数等待任务执行完毕。所以当你这样做时:

auto future = std::async(...

auto dstring = std::async([]{

它创建一个 std::future 类型的对象,当您离开 OutputDebugStringN 的范围时,它会调用 std::future 的析构函数,该析构函数会一直阻塞直到任务被执行。

在我看来,这是 C++ 的一个缺陷。这有点愚蠢(希望这不会冒犯任何人:p),它完全违背了异步的目的。为了获得大多数人期望的行为(显然,您期望它),您必须保留 std::future 对象的列表,然后花费精力(和处理时间)找出销毁单个 std 的正确时间: :future 列表中的对象。这是 OP 中问题 #1 的答案。对于#2,我认为您不应该在每个调试消息的基础上为此目的使用 std::async 。我认为它带来的问题多于解决的问题。

我不知道有什么优雅的方法可以解决这个问题。也许其他人可以插话。

至于我将如何实现异步版本的 OutputDebugString,我将只创建一个字符串的生产者-消费者队列。对此有很多问题,您可以谷歌生产者-消费者队列以获取详细信息。生产者是您发出消息的主线程。消费者是一个线程(或多个线程),从队列中挑选元素并调用 Window 的 OutputDebugString。

编辑:如果我冒犯了任何异步爱好者,我想补充一点,std::async 对于进行并行计算非常有用,就像使用 GPU 一样。我怀疑它是为利用并行硬件而设计的。例如:

      // merge sort
      {
           auto left =  std::async(.....);  // merge_sort left
           auto right =  std::async(.....);  // merge_sort right
      }

      merge

这样,在我们合并之前,左右都必须排序。如果没有,请等到它们都已排序,但它允许有机会并行处理左右两边。

如果您已经完成 CUDA 或任何 GPGPU 编码,这应该看起来很熟悉...

【讨论】:

  • 也许请注意,只有 std::futuresstd::async 块在其析构函数中创建。
  • @bamboon,说得好。我添加了这个以防有人试图推断。
  • 未来被毁灭听起来并不自然;谢谢你的帖子——它帮助我澄清了我对这一切的理解。我为实现示例选择了 claptrap 的答案,但您的答案最好地解释了这些概念。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多