Teigha 多线程高级 API(第 1 部分,共 3 部分)

简介

Teigha Kernel 提供了一个简单而强大的高级跨平台 API,称为线程池服务 (Thread Pool Services),用于在基于 Teigha 的应用程序和库中调用多线程功能。线程池服务由 Teigha Kernel 作为单独的扩展模块提供,可按需加载。它提供了一组用于处理线程、事件、多线程队列等的接口。

本文是关于线程池服务接口系列文章的一部分。

加载 ThreadPool.tx 模块

所有高级多线程接口都在一个头文件中声明。要调用它们,应用程序源代码必须包含此头文件:

#include "RxThreadPoolService.h"

线程池服务作为独立的 Teigha 扩展模块实现。应用程序可以加载此模块以访问主要的 OdRxThreadPoolService 模块接口:

// Load thread pool module
OdRxThreadPoolServicePtr pThreadPool = ::odrxDynamicLinker()->loadApp(OdThreadPoolModuleName);
if (pThreadPool.isNull())
  throw OdError(eNullPtr); // ThreadPool.tx not found.

静态链接的应用程序必须额外链接 ThreadPool.lib 静态库(非 Windows 平台为 ThreadPool.a)。此外,静态链接的应用程序必须在静态模块映射中注册 ThreadPool 模块:

/************************************************************************/
/* Define a module map for statically linked modules                    */
/************************************************************************/
#if !defined(_TOOLKIT_IN_DLL_) || defined(__MWERKS__)

ODRX_DECLARE_STATIC_MODULE_ENTRY_POINT(OdRxThreadPoolService);

ODRX_BEGIN_STATIC_MODULE_MAP()
  ODRX_DEFINE_STATIC_APPLICATION(OdThreadPoolModuleName, OdRxThreadPoolService)
ODRX_END_STATIC_MODULE_MAP()

#endif

加载 ThreadPool.tx 模块后,应用程序可以调用访问到的 OdRxThreadPoolService 模块接口来运行多线程操作。

使用线程池服务

我们可以通过两个不同的任务来说明线程池服务在实际应用程序中的使用:

  1. 在多个线程中加载和渲染数据库。
  2. 使用多个线程处理栅格图像。

类似或近似的任务可以应用于不同的客户端应用程序。

使用多个线程加载和渲染数据库

在多个线程中加载和渲染不同的数据库是多线程应用程序的典型任务。此示例对于实现您自己的应用程序非常有用,该应用程序调用多线程功能以优化处理大量图纸。

先决条件

我们将使用以下简单函数将数据库渲染为栅格图像:

// Simple function to render database into raster image
static OdGiRasterImagePtr renderDbToImage(OdDbDatabase *pDb, const OdChar *pRenderDevice, long picWidth, long picHeight)
{
  // Create vectorization context
  OdGiContextForDbDatabasePtr pDbCtx = OdGiContextForDbDatabase::createObject();
  // Create rendering device
  OdGsModulePtr pGsModule = ::odrxDynamicLinker()->loadModule(pRenderDevice);
  OdGsDevicePtr pDevice = pGsModule->createBitmapDevice();
  pDbCtx->setDatabase(pDb);
  // Initialize rendering device
  pDevice = OdDbGsManager::setupActiveLayoutViews(pDevice, pDbCtx);
  pDevice->setLogicalPalette(::odcmAcadDarkPalette(), 256);
  pDevice->setBackgroundColor(ODRGB(0, 0, 0));
  pDbCtx->setPaletteBackground(ODRGB(0, 0, 0));
  // Setup size of output contents
  pDevice->onSize(OdGsDCRect(OdGsDCPoint(0, picHeight), OdGsDCPoint(picWidth, 0)));
  // Zoom into model
  OdAbstractViewPEPtr(pDevice->viewAt(0))->zoomExtents(pDevice->viewAt(0));
  // Render
  pDevice->update();
  // Create clone of rendered raster image to correctly release all vectorizer resources in current thread
  OdGiRasterImagePtr pRaster = OdGiRasterImageHolder::createObject(OdGiRasterImagePtr(pDevice->properties()->getAt(OD_T("RasterImage"))));
  // Return rendered raster image
  return pRaster;
}

在多线程中运行数据库渲染之前,我们可以检查我们的 renderDbToImage 函数是否正常工作,以及矢量化模块是否已加载并可访问。此外,对空数据库进行初步渲染调用将分配静态模块资源,因此在此之后在多线程中运行将更安全:

{ // Check that rendering device can be normally loaded and works correctly. Additionally apply
  // rendering modules static data initialization.
  OdDbDatabasePtr pEmptyDb = svcs.createDatabase();
  ::renderDbToImage(pEmptyDb, OdWinOpenGLModuleName, 1024, 1024);
}

构建多线程队列

我们可以直接从 OdRxThreadPoolService 接口获取所需的线程数,但此解决方案会使编码复杂化。线程池服务提供多线程队列,可简化多线程工作。首先我们需要创建一个多线程队列:

OdApcQueuePtr pMTQueue = pThreadPool->newMTQueue(ThreadsCounter::kMtLoadingAttributes | ThreadsCounter::kMtRegenAttributes);

这通过对 OdRxThreadPoolService 接口的单次调用完成。由于我们将在多个线程中调用数据库加载和渲染,因此我们在 newMTQueue 方法中传递了一些附加标志;这些标志作为线程间缓存和缓冲区分配的提示。下表描述了所有标志值:

ThreadsCounter::kNoAttributes

可用于不需要任何特殊初始化的简单多线程进程。

ThreadsCounter::kMtLoadingAttributes

必须为在多个线程中加载多个数据库的进程设置。

ThreadsCounter::kMtRegenAttributes

必须为在数据库重新生成期间调用并行线程的进程设置。

ThreadsCounter::kStRegenAttributes

必须为在多个线程中调用矢量化的进程设置。

ThreadsCounter::kMtDisplayAttributes

必须为在多个线程中调用数据库显示的进程设置。

ThreadsCounter::kMtModelerAttributes

必须为在并行线程中调用建模操作的进程设置。

ThreadsCounter::kAllAttributes

启用所有属性。

在队列中运行多个线程

首先(对于简单的示例代码),我们使用一个附加结构,其中存储每个运行线程的实际渲染参数。指向此结构的指针将通过线程函数参数传递给每个运行线程。

// Helper structure with settings equal for all run threads
struct RenderDbToImageContext
{
  const OdChar *m_pRenderDevice;
  long m_picWidth, m_picHeight;
  OdDbHostAppServices *m_pServices;
  void setup(const OdChar *pRenderDevice, long picWidth, long picHeight, OdDbHostAppServices *pServices)
  { m_pRenderDevice = pRenderDevice; m_picWidth = picWidth; m_picHeight = picHeight; m_pServices = pServices; }
};

现在我们可以构建并填充这些结构数据成员:

// Create "render database to image" context shareable between threads
RenderDbToImageContext renderDbContext;
renderDbContext.setup(OdWinOpenGLModuleName, 1024, 1024, &svcs);

实现一个继承自 OdApcAtom 接口的类。在我们的示例中,此类型的对象对于每个线程都将不同(这简化了每线程数据的传递),但对于更简单的进程,我们可以为所有线程使用一个基于 OdApcAtom 的对象。

// Thread running method implementation
class RenderDbToImageCaller : public OdApcAtom
{
  OdString m_inputFile;
  OdGiRasterImagePtr *m_pOutputImage;
  public:
    RenderDbToImageCaller *setup(OdString inputFile, OdGiRasterImagePtr *pOutputImage)
    { m_inputFile = inputFile; m_pOutputImage = pOutputImage;
      return this; }
    virtual void apcEntryPoint(OdApcParamType pMessage)
    {
      RenderDbToImageContext *pContext = (RenderDbToImageContext*)pMessage;
      OdDbDatabasePtr pDb = pContext->m_pServices->readFile(m_inputFile);
      if (pDb.isNull())
        return;
      *m_pOutputImage = ::renderDbToImage(pDb, pContext->m_pRenderDevice, pContext->m_picWidth, pContext->m_picHeight);
    }
};

我们的示例 RenderDbToImageCaller 类只包含一个 OdApcAtom 接口:apcEntryPoint 方法。此重写方法将为每个线程调用。由于我们将 RenderDbToImageContext 结构指针作为线程参数传递,因此我们可以简单地将 pMessage 参数转换为此数据类型。apcEntryPoint 方法的实现只是从指定输入文件加载数据库,并使用前面描述的 renderDbToImage 函数将其渲染为栅格图像。

此时,我们已准备好为每个输入数据库文件运行一个单独的处理线程:

// 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);
}

在这里,我们为每个线程构造 RenderDbToImageCaller 对象,设置其参数并将其传递给 OdApcQueue::addEntryPoint 方法。作为 OdApcQueue::addEntryPoint 方法的第二个参数,我们传递一个指向 RenderDbToImageContext 结构的指针。队列将在调用 OdApcQueue::addEntryPoint 方法后立即在线程中运行处理;无需其他操作。

完成运行线程

由于我们将在执行更多步骤之前等待多线程处理的结果,因此我们必须确保所有线程都已完成其工作。此任务可以通过单个方法调用完成:

// Wait for threads completion
pMTQueue->wait();

OdApcQueue::wait 方法会等待所有线程完成,并将执行返回给调用者。在此方法调用之后,我们可以确保所有线程都已完成其任务,并且所有输出数据都可用于进一步处理。

下一篇文章将介绍如何使用多线程处理栅格图像。

今天就开始行动

免费试用 ODA 软件 60 天。
无风险,无需信用卡。

免费试用