4

使用 c++11 中的新 std::async,我想我可能会尝试实现 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 的异步版本?

对上述代码的批评和任何其他评论也非常受欢迎。

4

3 回答 3

9

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

因此,您的函数例如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");
...
于 2013-01-26T11:29:43.153 回答
3

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

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

于 2013-01-26T11:48:48.337 回答
2

对上述代码和 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 编码,这应该看起来很熟悉......

于 2013-01-26T11:40:46.297 回答