65.9K
CodeProject 正在变化。 阅读更多。
Home

快速、低延迟且非同步的跟踪类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (3投票s)

2004 年 4 月 17 日

4分钟阅读

viewsIcon

61003

downloadIcon

356

一篇关于现成可用的、专门针对多线程编程的跟踪类的文章。

引言

您是否曾经尝试使用通常提供的 TRACE()OutputDebugString() 等方法来查找棘手的多线程或同步 bug?您的调试版本运行良好,但您的发布版本却出现页面错误?

传统方法丢失了大量可以自动生成但需要您自己提供的信息。大多数宏只在调试模式下工作,在发布模式下不可用。

但最重要的一点是,使用调试工具会改变被调试应用程序的计时和流程。

我曾尝试在一个应用程序中查找线程同步 bug,并开发了此处提供的跟踪例程作为解决方案。

问题

第一个问题是计时,它会发生显著变化,例如使用 OutputDebugString()。不幸的是,时间取决于连接的调试器是否显示输出。

我在一个循环中进行了 100 万次跟踪,并测量了它的时间

TRACE-宏
调试代码
未连接调试器
28 秒。
TRACE-宏
调试代码
已连接 VS.NET 2003
1600 秒(基于 20,000 次跟踪)
TRACE-宏
调试代码
DbgView
210 秒(基于 100,000 次跟踪)
Tracer
调试代码
9 秒。
Tracer
发布代码
5 秒。

您可以看到,Tracer 速度快得多,并且即使在发布模式代码中也可用。

OutputDebugString() 带来的第二个问题是,该例程本身似乎是同步的。通过在两个并发线程中频繁使用此例程,您可能会因为 OutputDebugString() 的内部同步而改变计时 ***。

基础

跟踪类将它们的输出写入您驱动器上的普通文本文件。通过使用一些特殊的技巧,可以最大程度地减少同步和延迟。对于跟踪某些信息的每个线程,跟踪器会在应用程序目录中创建一个文件,命名为“<exe-name>. trace.<threadid>”。跟踪中的每一行都具有以下形式

00000128fd9f283f-00000320 : CCameraView::SetOptions() entered

第一部分是 QueryPerformanceCounter() 生成的硬件计时器值。第二个值是跟踪生成的线程 ID。通过使用提供的 TraceMerge.exe 实用程序,可以将不同的文件合并到一个大文件中,按时间排序。

跟踪通过环境变量开启和关闭。可以在启动调用中指定名称。

内部

跟踪代码实现为单例类。可以使用 CTracer::Get()->Trace() 访问单例。单例维护一个映射,其中包含每个线程的 TLSDataObject 对象。此类包含一个大的写入缓冲区,其中包含所有跟踪字符串和跟踪文件的句柄。提供了一个临界区来保护缓冲区。

当首次为特定线程进行跟踪时,跟踪器会创建一个 TLSDataObject,将其存储在映射和线程本地存储(TLS)索引中。通过使用 TLS,线程可以访问自己的 TLSDataObject,而无需与其他线程同步。只有插入映射(因此是第一次跟踪)需要同步。

Tracer 启动一个后台线程,该线程将每个线程的缓冲区写入文件。因此,在短时间内,跟踪必须停止。但请注意,只有后台线程和应用程序线程一次只有一个被链接在一起。

已关闭的线程会不时地从映射中删除,以释放写入缓冲区内存。

Using the Code

将“singleton.h”、“tracer.cpp”、“tracer.h”、“CritSecLocker.cpp”和“CritSecLocker.h”包含到您的项目中。在您要跟踪的所有源文件中包含“tracer.h”。

InitInstance()(或类似函数)中,使用以下方法启动跟踪:

CTracer::Get()->Startup("MYAPP_TRACE");

ExitInstance()(或类似函数)中,不要忘记调用:

CTracer::Get()->Shutdown();

在您的代码中,使用:

CTracer::Get()->Trace("A number %d",123);

来生成跟踪行。请注意,不允许附加“\n”,因为它会破坏合并工具。

如果您想刷新所有可用的缓冲区,可以使用:

CTracer::Get()->Flush();

这将写入所有线程的所有缓冲区,并刷新 Win32 文件缓冲区。例如,在异常处理程序中中止应用程序之前,这很有用。

如果您有一个执行大量跟踪的成员函数,提供一个局部引用会很有用:

void foo(void)
{
    CTracer &l_tracer = CTracer::Get();

    ...

    l_tracer->Trace("fewfewoifnwefewf");

    ...

    l_tracer->Trace("fewfewoifnwefewf");
}

使用 TraceMerger

为了从每个线程的各个文件创建一个跟踪文件,提供了一个小型实用程序。

像这样使用它

TraceMerger.exe <Application Exe Path>

TraceMerger 将获取所有跟踪文件,并根据时间合并排序。结果将保存到一个名为 <Application Exe Path>.trace.merge. 的新文件中。

许可证

本软件包根据 The Artistic License 获得许可。

*** 如果您对 OutputDebugString() 同步有任何了解,请告诉我……

© . All rights reserved.