0

我已经尝试并尝试在 SO 和整个 Internet 上找到关于此的预先存在的讨论。也许我做得不对,因为很难相信基本 C 语言没有解决这个问题。

我有一些计算简单的代码,但需要执行大量日志记录。此日志记录当前使我的代码运行速度慢了 10 倍。显然,解决方案是将日志记录中断到一个单独的线程或进程中。使用单独的进程需要大量特定于平台的 mumbo-jumbo,我需要此代码尽可能保持可移植性(目前在 OS X 中开发,稍后将移植到 Windows)。所以我刚刚开始学习线程和pthread.h。

需要明确的是,与 Internet 上一些现有的 pthread 讨论不同,我们在这里讨论的是直接写入文件,而不是 I/O。程序永远不需要读取日志文件。我现在正在做的是:

/* Called many times a second; a few minutes of program usage will make
a couple megs of text in the log file */
void MyLogFunction(char *format, ...)
{
    char buffer[1024];
    va_list arglist;

    va_start(arglist, format);
    vsnprintf(buffer, sizeof(buffer) - 1, format, arglist);
    va_end(arglist);

    pthread_t threadID;
    pthread_create(&threadID, NULL, WriteLog, &buffer);
}

/* As written, this function writes unpredictable contents to disk because
I passed it a pointer to a local variable that is getting modified
constantly by the main thread; I know I should be doing something like
allocating new blocks of memory within MyLogFunction() for each pthread,
so let's pretend I'm doing that and move on with the discussion ;-) */
void *WriteLog(void *string)
{
    pthread_detach(pthread_self());
    fprintf(gLogHandle, "%s: %s\n",
      /* OS X time-to-string stuff omitted here */,
      (char *)string);
    fflush(gLogHandle);

    return NULL;
}

我担心两件事:

A. 每秒产生多达几十个 pthread 似乎是不明智的。是吗?

B. 消息有时可能会乱写,对吧?

这导致我提出以下三个问题:

  1. 我应该创建一个保持打开状态的日志记录线程吗?它怎么知道什么时候写另一行而不快速轮询?我不能悠闲地轮询一些gNewLineIsAvailableToWrite布尔值,因为这段代码有时会崩溃,而且我不能错过崩溃前的最后一条日志消息。此外,日志中新行的创建每隔几个滴答就会达到峰值,每个滴答可能有 20 行。

  2. 我是否应该像当前显示的那样制作单独的 pthread,但给他们彼此的 ID 并告诉他们,pthread_join()以便他们在写他们的行之前等待前一个线程完成?这仍然会导致线程激增,但至少它们会按顺序写入,嗯?

  3. 还是我看这个问题都错了?

4

3 回答 3

2

1) 是的。当您使用某种形式的线程间通信允许它在生产者-消费者队列上等待日志 * 结构时,它将知道何时编写另一行。

2)不!不要不断地创建/终止/销毁线程。不要靠近join()。只需在启动时创建一个日志线程,然后将您的日志请求排队。

或者,当然,找到一个已经可以工作的日志库:)

另一个提示:您可以在结构中添加一个“命令”枚举,告诉日志记录线程该做什么。当然,大多数情况下都是“将字符串记录在我的缓冲区中”,但是您可以添加其他命令,例如,刷新日志文件,使用缓冲区中的新路径/文件名打开一个新的日志文件,打开一个X MB 或每 X 小时后的新日志文件,无论如何。队列的“松弛”将允许“冗长”的文件操作,而不会对请求日志记录的线程的性能产生任何影响。

于 2013-11-04T14:49:18.460 回答
1

创建一个单独的的线程显然不是最理想的(更不用说受到来自多个线程的输出错误的影响)。登录多线程应用程序的一种方法是登录到内部(通常是循环)缓冲区,该缓冲区由专门指定的线程定期刷新。结果,您在每个线程中登录的“I/O”成本显着下降(尽管格式化仍然需要付出代价)。但是,您必须为争夺日志资源(缓冲区)付费。也有一些方法可以减轻它,但仍然会有一些成本。

说了以上所有内容,如果不查看程序的性能配置文件(有多少线程,它们竞争的内容等),很难评估处理它的正确方法。如果我是你,我会从尝试开始一个现成的日志基础设施,如log4cpp。很多人已经解决了你面临的问题,没有理由重新发明它。尝试使用它,然后看看你的瓶颈在哪里。

于 2013-11-04T14:56:51.590 回答
0
  1. 我应该创建一个保持打开状态的日志记录线程吗?

是的,通常的方法是拥有一个单一的、长期存在的日志记录线程。

...它怎么知道什么时候写另一行而不快速轮询?

标准解决方案是使用受互斥体保护的共享日志队列和关联的条件变量来唤醒日志线程(最好只在队列不再为空时才需要这样做)。

...此外,日志中新行的创建每隔几个滴答声就会达到峰值,每个滴答声可能有 20 行。

尝试确保您使用的任何共享队列都可以吸收这些峰值,并注意没有理由记录线程必须在每个锁定/等待/解锁周期只消耗一条消息。

于 2013-11-04T15:06:58.913 回答