Teighaマルチスレッド低レベルAPI(4部構成の第2部)

この記事は、マルチスレッドに使用される低レベルAPIに関する一連の記事の一部です。最初の記事については、パート1を参照してください。

Teigha組み込みミューテックスオブジェクト

Teigha Kernelは、ミューテックスオブジェクトのクロスプラットフォーム実装を提供します。ミューテックス(相互排他)オブジェクトの説明については、Wikipediaを参照してください。

ミューテックスオブジェクトはOdMutexクラスにラップされています。このクラスには2つのメソッドしかありません。

void lock();
void unlock();

OdMutex::lockメソッドは、例えば複数のスレッドからアクセス可能な共有リソースを含むクリティカルセクションの開始時に呼び出すことができます。OdMutex::unlockメソッドはクリティカルセクションの終了時に呼び出す必要があります。OdMutex::lock/OdMutex::unlockメソッドは共有リソースアクセス用のスコープを表すため、常にペアで呼び出す必要があります。OdMutex::lockメソッドを使用した後にOdMutex::unlockメソッドが呼び出されない場合、コードセクションは無限にロックされた状態に保たれ、アプリケーションは無限ループに入ります。スレッドがOdMutex::unlockメソッドを呼び出すと、OdMutex::lockメソッド呼び出し内で待機している他のスレッドのゲートが開きます。ミューテックスオブジェクトは再入可能であり、これはロックされたミューテックスを持つ同じスレッドがロックされたセクションに何度も再入できることを意味しますが、もちろん、その後の各再入後にはミューテックスのロック解除を呼び出す必要があり、これによりクリティカルセクション内のスレッドの再入回数が減少します。最終的なOdMutex::unlockメソッド呼び出しのみが、他のスレッドのためにクリティカルコードセクションのロックを解除します。

ミューテックスオブジェクトの操作を簡素化するために、OdMutexAutoLockクラスを使用できます。これは、コンストラクタでOdMutex::lockメソッドを呼び出し、デストラクタでOdMutex::unlockメソッドを呼び出すだけであり、これによりロック/アンロックメソッドのペアが一緒に呼び出されることが保証され、開発者がクリティカルセクションを離れるときにミューテックスのロック解除を忘れることがなくなります。

ミューテックスオブジェクトの使用例

ミューテックスオブジェクトの使用を実演するために、この一連の記事のパート1で紹介したマルチスレッド画像処理の例を拡張します。複数のスレッドからの出力テキスト文字列を1つの大きなテキスト文字列に蓄積する新しいExampleMtStringAccumulatorクラスを追加します。

// Example of multithread strings accumulator invoking mutex object
class ExampleMtStringAccumulator
{
  protected:
    OdString m_outputString;
    OdMutex m_mutex;
  public:
    ExampleMtStringAccumulator() {}
    ~ExampleMtStringAccumulator() {}
    void addString(const OdString &str)
    {
      TD_AUTOLOCK(m_mutex)
      m_outputString += str;
    }
    const OdString &getString() { return m_outputString; }
};

ExampleMtStringAccumulator::addStringメソッドは複数のスレッドから呼び出されるため、文字列連結中のスレッド間の競合を避けるために、ミューテックスオブジェクトを使用して文字列連結操作を保護します。TD_AUTOLOCKマクロは、マルチスレッドTeigha構成のためにOdMutexAutoLockクラスを呼び出すだけなので、ここでは最終的なコードを簡素化するために使用されています。あるいは、OdMutexAutoLockクラスをそのまま使用することもできます。

OdMutexAutoLock autoLock(m_mutex);

次に、メインの例関数でExampleMtStringAccumulatorクラスのインスタンスを構築します。

// Multithread output strings accumulator
ExampleMtStringAccumulator stringsAccum;

ProcessImageCallerクラスを拡張して、ExampleMtStringAccumulatorクラスを通じて処理したスキャンライン番号を出力するようにします。

// Thread running method implementation
class ProcessImageCaller : public OdRxObject
{
  OdSmartPtr<ProcessedRasterImage> m_pProcImage;
  OdUInt32 m_scanLineFrom, m_nScanLines;
  ExampleMtStringAccumulator *m_pOutput;
  public:
    ProcessImageCaller *setup(ProcessedRasterImage *pProcImage, OdUInt32 scanLineFrom, OdUInt32 nScanLines)
    { m_pProcImage = pProcImage; m_scanLineFrom = scanLineFrom; m_nScanLines = nScanLines;
      return this; }
    static DWORD WINAPI entryPoint(LPVOID pArg)
    {
      ::odThreadsCounter().startThread();
      ProcessImageCaller *pCaller = (ProcessImageCaller*)pArg;
      pCaller->m_pProcImage->process(pCaller->m_scanLineFrom, pCaller->m_nScanLines);
      pCaller->m_pOutput->addString(OdString().format(OD_T("Thread processed scanlines from %u to %u\n"),
                                    (unsigned)pCaller->m_scanLineFrom, (unsigned)(pCaller->m_scanLineFrom + pCaller->m_nScanLines - 1)));
      ::odThreadsCounter().stopThread();
      return EXIT_SUCCESS;
    }
    ProcessImageCaller *run(SimpleWinThreadsPool &threadPool, ExampleMtStringAccumulator *pOutput)
    {
      m_pOutput = pOutput;
      threadPool.runNewThread(entryPoint, this, ThreadsCounter::kNoAttributes);
      return this;
    }
};

これで、ExampleMtStringAccumulatorクラスのインスタンスを各ProcessImageCallerインスタンスに渡し、すべてのスレッドが完了した後に蓄積された文字列をコンソールに出力できます。

// Run threads for raster image processing
const OdUInt32 nScanlinesPerThread = pProcImage->pixelHeight() / 4;
for (OdUInt32 nThread = 0; nThread < 4; nThread++)
{
  OdUInt32 nScanlinesPerThisThread = nScanlinesPerThread;
  if (nThread == 3) // Height can be not divided by 2, so last thread can have one scanline less.
    nScanlinesPerThisThread = pProcImage->pixelHeight() - nScanlinesPerThread * 3;
  lockedObjects.push_back(
    OdRxObjectImpl<ProcessImageCaller>::createObject()->
      setup(pProcImage, nScanlinesPerThread * nThread, nScanlinesPerThisThread)->
        run(winThreadPool, &stringsAccum));
}

// Wait for threads completion
winThreadPool.wait();
lockedObjects.clear();

// Output accumulated string
odPrintConsoleString(stringsAccum.getString());

最終的な出力は次のようになります。

4つのファイルが0.421743秒でロードされ、レンダリングされました
スレッドは768から1023までのスキャンラインを処理しました
スレッドは512から767までのスキャンラインを処理しました
スレッドは0から255までのスキャンラインを処理しました
スレッドは256から511までのスキャンラインを処理しました
最終ラスタ画像は0.583598秒で処理されました

組み込みのTeighaスレッドIDアクセサーの操作とマルチスレッド低レベルAPIの使用に関する別の記事が近日公開されますので、ご期待ください。

今すぐ始める

ODAソフトウェアを60日間無料でお試しください。
リスクなし、クレジットカード不要。

無料で試す