はじめに
Teigha Kernelは、Teighaベースのアプリケーションやライブラリ内でマルチスレッド機能を呼び出すために使用される、シンプルで強力な高レベルのクロスプラットフォームAPIであるスレッドプールサービスを提供します。スレッドプールサービスは、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モジュールインターフェースは、マルチスレッド操作を実行するためにアプリケーションによって呼び出すことができます。
スレッドプールサービスの使用
スレッドプールサービスの使用法を、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インターフェースを継承したクラスを実装します。この例では、この型のオブジェクトはスレッドごとに異なりますが(これによりスレッドごとのデータ受け渡しが簡素化されます)、より単純なプロセスでは、すべてのスレッドに1つの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クラスには、apcEntryPointメソッドという単一のOdApcAtomインターフェースのみが含まれています。このオーバーライドされたメソッドは、各スレッドに対して呼び出されます。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メソッドの2番目の引数として、RenderDbToImageContext構造体へのポインタを渡します。OdApcQueue::addEntryPointメソッドが呼び出された後、キューはすぐにスレッドで処理を実行します。追加のアクションは必要ありません。
実行中のスレッドの完了
さらなるステップに進む前にマルチスレッド処理の結果を待つため、すべてのスレッドがジョブを完了していることを確認する必要があります。このタスクは、単一のメソッド呼び出しで実行できます。
// Wait for threads completion
pMTQueue->wait();
OdApcQueue::waitメソッドは、すべてのスレッドが完了するまで待機し、呼び出し元に実行を返します。このメソッド呼び出しの後、すべてのスレッドがタスクを完了し、すべての出力データがさらなる処理に利用可能であることを確認できます。
次の記事では、複数のスレッドを使用したラスター画像の処理について説明します。