本文是关于用于多线程的线程池服务接口系列文章的一部分。有关上一篇文章,请参阅第 1 部分和第 2 部分。
测量性能
为了进行性能测量,我们包含了一个 Teigha 内核性能计时器:
#include "OdPerfTimer.h"
And construct it in our main application function:
// Init performance timer
OdPerfTimerWrapper perfTimer;
这里我们使用了 OdPerfTimerWrapper 性能计时器包装器。使用该包装器简化了我们的代码,因为它将在 OdPerfTimerWrapper 类构造函数中自动为我们构造一个性能计时器,并在我们的主可执行函数结束时在 OdPerfTimerWrapper 类析构函数中销毁它。
现在我们可以使用 OdPerfTimerBase 的 start 和 stop 方法来包装需要性能测量的源代码部分:
// Start timing for "render database to image" process
perfTimer.getTimer()->start();
// Run loading and rendering process
for (OdUInt32 nInput = 0; nInput < generatedRasters.size(); nInput++)
{
OdString inputFileName(argv[2 + nInput]);
pMTQueue->addEntryPoint(OdRxObjectImpl<RenderDbToImageCaller>::createObject()->setup(inputFileName, &generatedRasters[nInput]), (OdApcParamType)&renderDbContext);
}
// Wait for threads completion
pMTQueue->wait();
pMTQueue.release();
// Output timing for "render database to image" process
perfTimer.getTimer()->stop();
odPrintConsoleString(L"%u files loaded and rendered in %f seconds\n", generatedRasters.size(), perfTimer.getTimer()->countedSec());
start 和 stop 方法的范围包括线程执行和等待线程执行结果。最后,我们以秒为单位输出测量结果。类似地,放置 OdPerfTimerBase 的 start 和 stop 方法范围来测量光栅图像处理的性能。测量结果如下所示:
4 个文件在 0.509886 秒内加载并渲染
最终光栅图像在 0.542890 秒内处理
这是启用多线程后的结果,但我们应该将其与单线程解决方案的结果进行比较,以确保我们实现了真正的优化。对于此任务,我们可以简单地将多线程队列的使用更改为单线程队列:
// OdApcQueuePtr pMTQueue = pThreadPool->newMTQueue(ThreadsCounter::kMtLoadingAttributes | ThreadsCounter::kMtRegenAttributes);
OdApcQueuePtr pMTQueue = pThreadPool->newSTQueue();
OdRxThreadPoolService::newSTQueue 方法返回一个类似的 OdApcQueue 接口,就像 OdRxThreadPoolService::newMTQueue 方法一样,因此代码的其他部分不需要为性能测量进行任何更改;OdApcQueue::addEntryPoint 和 OdApcQueue::wait 方法可以像以前一样调用。与多线程队列的区别在于,单线程队列在一个专用线程中执行所有请求的任务。
现在我们可以看到不同的性能测量结果:
4 个文件在 1.081148 秒内加载并渲染
最终光栅图像在 1.824529 秒内处理完成
启用多线程后,我们的库配置和环境数据库的加载和渲染过程大约快了两倍。启用多线程后,光栅图像处理大约快了三倍。这种差异是因为与数据库加载和矢量化过程相比,光栅图像处理不需要线程之间共享资源和内存访问的同步。
结论
线程池服务 API 的使用可以显著简化多线程解决方案的实现。此 API 不仅包含用于处理线程和队列的接口,还包含一组用于多线程同步的接口。
例如,采用以跨平台方式实现的经典事件对象,例如 OdApcEvent 接口。OdApcEvent 可用于简单的多线程同步任务。它对于共享资源访问很有用。例如,第一个线程在进入可能在多线程访问期间引起冲突的代码部分之前调用 OdApcEvent::reset 方法,并在离开此代码部分后调用 OdApcEvent::set 方法。第二个线程可以使用 OdApcEvent::wait 方法等待直到第一个线程调用 OdApcEvent::set 方法,然后进入可能在多线程访问期间引起冲突的代码部分,并可以使用第一个线程处理的结果。等待线程不使用任何 CPU 资源,因此这是多线程同步的正常解决方案。使用线程同步对象可确保多个线程稳定地处理关键代码段,并且多线程处理的结果不会损坏。
线程池服务 API 还包含更复杂的同步对象:OdApcGateway 和 OdApcLoopedGateway。但它们的描述超出了本文的范围。
未来的博客文章将继续讨论 Teigha 的多线程功能。它将描述低级 Teigha Kernel 多线程 API 以及它如何与高级线程池服务 API 进行通信。