API ガイド > テクニカル ノート >

# スレッドと Maya API

ある特定のガイドラインに従えば、スレッド化されたコードをプラグインで利用できます。以下にあげるのは、この場合の注意事項です。

1. Maya は次のタイプのスレッドを使用します。
   - Linux - pthread
   - Mac OS X - pthread
   - Windows - Native Windows API スレッド
2. API で使用可能な Maya のコンポーネントは単一スレッドです。Maya メイン スレッドから Maya API に呼び込むことが最善です。Maya API へのコールから独立しているかぎり、コードをスレッド化することは可能です。この規則の例外は MPxNode::compute() で、ソフトウェア シェーダ用の複数のスレッド コールを持つことができます。ただしこれは、ノードとソフトウェア レンダラの設定に依存します。
3. Maya の操作にはスレッド化されているものもありますが、API では公開されません。
4. MGlobal::executeCommandOnIdle() メソッドを使用すると、補助スレッドから Maya に呼び込むことが可能です。Python では、同等の MGlobal::executePythonCommandOnIdle() メソッドを使用します。コマンドが即座に実行されます。あるいは、コマンドはアイドル状態のイベント待ち行列に追加されて、アイドル処理が許可されると実行されます。コマンドの結果はコール側に返されません。このコールは、別のスレッドの進捗バーなどの項目の更新に便利です。
5. スレッド化に使用できる C++ API クラスには、MThreadPool、MThreadAsync、MSpinLock、MMutexLock の 4 種類があります。これらのクラスを利用し、Maya 以外の API 機能に対してスレッド アルゴリズムを実装することができます。開発キットには、これらのクラスの使用例がいくつか用意されています(Maya Python API では、これらのクラスは使用できません)。

   MThreadPool は、タスクの割り当てが可能なスレッド プールへのアクセスを提供します。タスクの数は、スレッドの数と同じである必要はありません。実際、負荷を分散させるには、スレッド数よりもタスク数の方が多いことが望ましいです。Maya では、最大限に効率化するために、複数のスレッドの間で内部的に作業が分散されます。プール内のスレッドの数は、論理プロセスの数と同じです。使用のたびにスレッド プールを削除する必要はありません。むしろ、平行領域が完了したときにスレッドは休止状態に入る、つまりスレッドはすばやく再起動できるので、パフォーマンス上の理由から言っても削除はしないことをお勧めします。

   MThreadAsync では、長期間実行可能なスレッドを 1 つまたは複数個作成できます。これらは MThreadPool により作成および管理されるスレッド プールから導出されたものではなく、独立したスレッドです。これらのスレッドを使用すると、タスクを長時間実行できます。これらはスレッド プールから作成されるものではないので、サブスクリプションの超過(活動中のスレッド数が使用可能なハードウェア リソースを超過している状態)を避けるためにも、このようなスレッドの数や負荷は慎重に管理する必要があります。

   MMutexLock は、MThreadPool と MThreadAsync の両方のスレッドで使用可能なロッキング プリミティブです。スレッドの標準 Mutex ロッキングが可能です。

   MSpinLock はスピン待機するロックなので、ロックが非常に短時間維持されるような状況では、Mutex ロックよりも効率的です。ただし、ロックはスピン待機されるため、大量の CPU が消費されますから、長時間、ロックしたままになるような状況では使用すべきではありません。

6. ビルトインのスレッド モジュールを使うと、Python を使用したスレッド化が可能です。このスレッド モジュールは、Maya 以外の API 機能に対するスレッド アルゴリズムの実装に使用できます。詳細については、『Python』マニュアルの「Python とスレッディング」セクションを参照してください。

次の例は、シリアルとスレッド化のアプローチを使用して、素数を検索する方法を示しています。スレッド化アプローチでは、MThreadPool クラスが使用されています。

    #include <math.h>
    #include <maya/MIOStream.h>
    #include <maya/MSimple.h>
    #include <maya/MTimer.h>
    #include <maya/MGlobal.h>
    #include <maya/MThreadPool.h>
    DeclareSimpleCommand( threadTestCmd, PLUGIN_COMPANY, "2013");
    typedef struct _threadDataTag
    {
        int threadNo;
        long primesFound;
        long start, end;
    } threadData;

    typedef struct _taskDataTag
    {
        long start, end, totalPrimes;
    } taskData;

    #define NUM_TASKS	16
    // No global information used in function

    static bool TestForPrime(int val)
    {
        int limit, factor = 3;
        limit = (long)(sqrtf((float)val)+0.5f);
        while( (factor <= limit) && (val % factor))
            factor ++;
        return (factor > limit);
    }

    // Primes finder. This function is called from multiple threads
    MThreadRetVal Primes(void *data)
    {
        threadData *myData = (threadData *)data;
        for( int i = myData->start + myData->threadNo*2; i <= myData->end; i += 2*NUM_TASKS )
        {
            if( TestForPrime(i) )
            myData->primesFound++;
        }
        return (MThreadRetVal)0;
    }

    // Function to create thread tasks
    void DecomposePrimes(void *data, MThreadRootTask *root)
    {
        taskData *taskD = (taskData *)data;
        threadData tdata[NUM_TASKS];
        for( int i = 0; i < NUM_TASKS; ++i )
        {
            tdata[i].threadNo = i;
            tdata[i].primesFound = 0;
            tdata[i].start = taskD->start;
            tdata[i].end = taskD->end;
            MThreadPool::createTask(Primes, (void *)&tdata[i], root);
        }
        MThreadPool::executeAndJoin(root);
        for( int i = 0; i < NUM_TASKS; ++i )
        {
            taskD->totalPrimes += tdata[i].primesFound;
        }
    }

    // Single threaded calculation
    int SerialPrimes(int start, int end)
    {
        int primesFound = 0;
        for( int i = start; i <= end; i+=2)
        {
            if( TestForPrime(i) )
                primesFound++;
        }
        return primesFound;
    }

    // Set up and tear down parallel tasks
    int ParallelPrimes(int start, int end)
    {
        MStatus stat = MThreadPool::init();
        if( MStatus::kSuccess != stat ) {
            MString str = MString("Error creating threadpool");
            MGlobal::displayError(str);
            return 0;
        }

        taskData tdata;
        tdata.totalPrimes = 0;
        tdata.start = start;
        tdata.end = end;
        MThreadPool::newParallelRegion(DecomposePrimes, (void *)&tdata);
        // pool is reference counted. Release reference to current thread instance
        MThreadPool::release();
        // release reference to whole pool which deletes all threads
        MThreadPool::release();
        return tdata.totalPrimes;
    }

    // MSimple command that invokes the serial and parallel thread calculations
    MStatus threadTestCmd::doIt( const MArgList& args )
    {
        MString introStr = MString("Computation of primes using the Maya API");
        MGlobal::displayInfo(introStr);
        if(args.length() != 2) {
            MString str = MString("Invalid number of arguments, usage: threadTestCmd 1 10000");
            MGlobal::displayError(str);
            return MStatus::kFailure;
        }
        MStatus stat;
        int start = args.asInt( 0, &stat );
        if ( MS::kSuccess != stat ) {
            MString str = MString("Invalid argument 1, usage: threadTestCmd 1 10000");
            MGlobal::displayError(str);
            return MStatus::kFailure;
        }
        int end = args.asInt( 1, &stat );
        if ( MS::kSuccess != stat ) {
            MString str = MString("Invalid argument 2, usage: threadTestCmd 1 10000");
            MGlobal::displayError(str);
            return MStatus::kFailure;
        }

        // start search on an odd number
        if((start % 2) == 0 ) start++;
        // run single threaded
        MTimer timer;
        timer.beginTimer();
        int serialPrimes = SerialPrimes(start, end);
        timer.endTimer();
        double serialTime = timer.elapsedTime();
        // run multithreaded
        timer.beginTimer();
        int parallelPrimes = ParallelPrimes(start, end);
        timer.endTimer();
        double parallelTime = timer.elapsedTime();
        // check for correctness
        if ( serialPrimes != parallelPrimes ) {
            MString str("Error: Computations inconsistent");
            MGlobal::displayError(str);
            return MStatus::kFailure;
        }
        // print results
        double ratio = serialTime/parallelTime;
        MString str = MString("\nElapsed time for serial computation: ") + serialTime + MString("s\n");
        str += MString("Elapsed time for parallel computation: ") + parallelTime + MString("s\n");
        str += MString("Speedup: ") + ratio + MString("x\n");
        MGlobal::displayInfo(str);
        return MStatus::kSuccess;
    }