マルチプロセッサ/マルチコアプロセッサの並列処理方法——マイクロスレッド


 

上記の2つの論文では,比較的典型的で簡単な並列方法を紹介し,それらの性能と利点と欠点も簡単に紹介した.ここでは、マイクロスレッドを介して複数の相互協力パラレルタスクを同期する方法について説明します.
1次元配列のような既知の長さの巨大な線形テーブルで要素を検索し、その要素が一意であることを想像してみましょう.現在使用しているプロセッサには2つのコアがあり、前述した2つ目の並列方法によれば、コアAに前半の要素を検索させ、コアBに後半の要素を検索させることができます.当時,探索操作を単独のスレッドとして処理した.しかし、検索のような操作は、終了後すぐに結果を得て、次に他のことをします.このとき,この探索操作はあるスレッド(タスク)のシリアル(シーケンス操作)部分となる.しかもこれも自然に見えます.しかし、これは、1つのコアが結果を見つけたときに、別のコアに検索動作を停止させ、すぐに後のことを処理する方法にも問題をもたらした.ここでは,この状況をより効果的に処理するために「マイクロスレッド」の概念を導入したい.
ここで、「マイクロスレッド」とは、スレッドに依存し、そのスタック空間がその依存するスレッドのスタック空間のサブ区間であり、マイクロスレッドの動作がスレッドのジョブフローの一部となり、マイクロスレッドの動作が完了した後にのみスレッドの残りの部分が実行されるスレッドを指す.
次のモデルを使用できます.
int ThreadA_Handler(void)
{
    Initialize();

    while(do_event(event_id1))
    {
        void* mem = AllocMemPool(MEM_SIZE_1024B);
        Initialize_Resource(mem);

        // Create micro-thread
        int elem = StartMicroThread(
                         /*thread id*/task_id_threadA,
                         /*micro-thread handler*/&do_handlerMA,
                         /*the argument for the handler*/mem,
                         );

        // Process the result
        Output(elem);
    }
    return THREAD_SUCCESS;
}

上記のコードでは、StartMicroThread()関数は、マイクロスレッドを作成するためのインタフェースです.後述する割り込み処理におけるマイクロスレッド割り当て処理に用いる現在のスレッドのIDを記録する.その後、マイクロスレッド処理関数を渡します.マイクロスレッドの入力パラメータを再入力します.
StartMicrosThread()の可能性について説明します.
void* StartMicroThread(TASK_ID task_id, /*input*/
                       void*(*pMicroThreadHandler)(void*),/*input*/
                       void* pParam,  /*input*/ 
                      )
{
    unsigned int context = sys_fetch_ret_address();
    RegisterMicroThreadHandler(task_id, context);
    void* ret = (*pMicroThreadHandler)(pParam);
    return ret;
}

上記のコードではsys_fetch_ret_address()システム関数は、現在の関数を取得するために返されるアドレスを表し、外部イベントがマイクロスレッドの操作を直接キャンセルしてStartMicrosThread()の関数呼び出しの次の命令アドレスに移動することができ、もちろん、ここでは使用されるスタック空間を回収するために処理を加えることができる.StartMicroThread()関数呼び出しの前後にコンテキストの保護とリカバリを追加することができる(ここでのコンテキスト保護とリカバリは、スタックポインタとフレームポインタ(x 86ではベースポインタBPと呼ばれる)に対する保護とリカバリにほかならない).
RegisterMicrosThreadHandler()は、スレッドIDを登録し、アドレスを返すために使用されます.一方のコアが他方のコア動作が完了したことを通知し、他方のコアが直ちにマイクロスレッド動作を終了すると、割込み処理では、現在のアクティブスレッドが登録済みスレッドであるか否かを判断し、もし、割込み戻りアドレスをcontext値に設定する.そうでなければ、スレッドにフラグビットを設定し、context値を渡して、スレッドが再スケジュールされたときにマイクロスレッド操作を直ちに終了させることができる.従って,このマイクロスレッド概念もスケジューリング前処理メカニズムに基づいており,このメカニズムは現在多くの主流オペレーティングシステムでは使用されていない.
3番目の文は、ユーザーがカスタマイズしたマイクロスレッド処理関数を呼び出し、結果を返すことです.ここで、結果は、マイクロスレッド操作が完全な操作が完了していない場合に強制的に終了する可能性が高いため、共有ストレージ領域の変数に返されることが多い.この場合、戻り値は不定である.したがって、ここで戻り値を設定するのは良いアイデアではないかもしれません.
次に、マクロ関数を使用してスタックコンテキストの保護とリカバリを追加します.
#define BEGIN_MICRO_THREAD(task_id, handler, param) {  /
    SAVE_STACK_CONTEXT();    /
    StartMicroThread(task_id, handler, param);    /
    RESTORE_STACK_CONTEXT();    /
}

SAVE_についてSTACK_CONTEXT()とRESTORE_STACK_CONTEXT()は、特定のシステムに従って実現することができる.もちろん、インラインアセンブリの形式を使っても全く問題ありません.
その後、マイクロスレッド操作でストレージリソース、ロック、または他のリソースを申請する場合があります.この場合、強制終了操作により解放されなかったリソースを処理するために、いくつかの手段を利用することもできる.この問題は,スレッドにおけるリソース申請や解放の操作と差が少なく,参考になる.C++のようなtry−catch機構も参考に値する.
最後にもう一つの問題は無視できない.すなわち、マイクロスレッド処理関数を終了した後、登録されたコンテキストをログアウトしなければならない.マクロ関数をもう1つ定義できます
#define END_MICRO_THREAD(task_id) {    /
    UnregisterMicroThreadHandler(task_id);    /
    // You may add some resource releasing function calls here    /
}

これにより、2つのマクロ関数がちょうどペアで使用されます.
次に,マイクロスレッド処理関数における動作のより具体的な問題について議論する.
やはり検索操作を例に挙げます.このメカニズムの下で,結果を得たマイクロスレッドに付属するスレッドが存在するコアを決定することが基本的に必要である.この点,マイクロスレッドメカニズムは組み込みシステムに適している.もう1つのマイクロスレッドは計算の補助動作として機能する.一般に、割り込み要求信号は、常に補助動作が結果スレッドが存在するコアに送信され、それが動作を完了し、結果が得られたマイクロスレッドが動作を完了していない場合に送信される.一方、結果を得たマイクロスレッドは、操作が完了した後、操作結果が得られなかった場合(例えば、指定されたデータが検索されなかった場合)、別のコアの操作が完了するのを待たなければならない.従って、このマイクロスレッドは、適切な結果が得られた場合にのみ、その動作を本当に終了し、スレッドが実行を継続する.これも、コンテキストを登録およびログアウトする関数が、他のフラグを必要とせずに1つのスレッドIDしか必要としない理由です.
補助計算としてのマイクロスレッドは、結果を得たマイクロスレッドと同じコアにスケジューリングすることができる.この場合、効率も低下しません.特に、別のコアが他のトランザクションの処理に追われ、マイクロスレッドが属するスレッドを長時間スケジューリングできない場合です.
最後に、マイクロスレッド処理関数の実装についても参照してください.http://download.csdn.net/source/297300