Max 5 API Reference

スレッド

Max の systhread API には主な目的が2つあります。

第1に、これを使ってスレッド保護を実装することです。このスレッド保護はクロスプラットフォームで、 Max の既存のスレッドモデルと連携して動作します。スレッド保護は、同じアプリケーションで同時に実行されているスレッドでのデータの破損を防止します。この章では、Max のスレッドモデルについて説明し、スレッド保護のシンプルな例を紹介します。また、多くの場合、Max が提供する データ記憶メカニズム を使うことによって、スレッド保護を使用する必要がないようにすることも可能です。

第2は、これを使って、クロスプラットフォームな方法でスレッドの生成と管理を行なうことです。これは、極めてわずかなプログラマにしか必要とされなかった先進的な機能です。スレッドの生成と管理を行なうための情報は systhread API のヘッダファイルを参照して下さい。

 

Max のスレッド処理

ここで説明する Max によるスレッド処理の方法は変更されることがあり、将来のバージョンでは適用されないかもしれないという点に注意して下さい。Max スケジューラや低い優先度のキューについての詳しい情報は スケジューラ というセクションを参照して下さい。

Max (オーディオを除きます)には2つのスレッドがあります。メインスレッド、あるいはイベントスレッドと呼ばれるものは、ユーザによるインタラクション、システムに対するスクリーンの再描画の要求、低い優先度のキューでのイベント処理を扱います。オーバードライブ(Overdrive)モードでない場合、メインスレッドはMax スケジューラの中のイベントの実行も同様に扱います。オーバードライブが使用可能(イネーブル)になっている場合、スケジューラは高い優先度のタイマスレッドで動作します。これは、オペレーティングシステムによってもたらされる性能限界の範囲内で、ユーザの初期設定によって設定された正確なスケジューラインターバルで実行しようとします。このインターバルはおよそ 1 〜 2 ミリ秒です。

基本的な考え方は、正確なタイミングが必要で比較的コンピュータに対する負荷の小さい処理を高い優先度のスレッドに置き、それほど正確なタイミングを必要とせずコンピュータに対する負荷が大きいイベントをメインスレッドに置くというものです。マルチコアのマシンでは、高い優先度のスレッドは異なったコアで実行される可能性も、そうでない可能性もあります。

Mac と Windows のいずれのプラットフォームでも、通常タイマスレッドのシステム優先度レベルは非常に高いのですが、その場合でも、メインスレッド、あるいはタイマスレッドのどちらかが、もう一方のスレッドに割り込みをかける可能性があります。これは、全く適切でないように思えますが、オペレーティングシステムはこのような動作を行ないます。例えば、Max のタイマスレッドがあまりにも時間を使いすぎていると OS が判断した場合、たとえ、他のスレッドが低いシステム優先度しか持っていない場合でも、OS は他のスレッドによって Max のタイマスレッドに割り込みをかけ、これを「咎める」でしょう。

どのスレッドも他のスレッドによって割り込まれる可能性があるという理由から、特定の型のデータ構造や論理演算の完全性を保つためにスレッド保護を使用する必要があります。この良い例としてリンクリストがあります。リンクリストでは、リストの変更を行なっているスレッドが、このリストを変更しようとする他のスレッドに割り込まれた場合、リストは壊れてしまいます。Max の t_linklist というデータ構造はスレッドセーフです(スレッド保護が行なわれています)。そのため、このようなデータ構造を必要とする場合には t_linklist の使用を推奨します。さらにMax では、メッセージの送信やアウトレットの使用などのような一般的な操作の多くでタイマスレッドとメインスレッド間でのスレッド保護が行なわれています。

これにオーディオ処理を追加した場合、スレッドの状態はより複雑なものになります。オーディオのパフォームルーチンは、オーディオハードウェアドライバによってコントロールされるスレッドの内部で実行されます。過度のスレッド・ブロッキングや潜在的な競合状態を取り除くために、オーディオパフォームルーチン内で提供されるスレッド保護は狭い範囲に限られています。また、API ドキュメントの MSP セクションの中で述べているように、パフォームルーチンとMax の通信のためにサポートされる操作は clock の使用のみに限定されています。clock はMax スケジューラの中で関数をトリガし、実行させるものです。

Max スケジューラは、様々に異なったスレッドの状態の中で動作することが可能です。すでに述べたように、スケジューラはメインスレッドの中でも、あるいはタイマスレッドの中でも実行できます。オーディオ割り込みの中のスケジューラ(Scheduler in Audio Interrupt : SIAI)が使用可能(イネーブル)である場合、スケジューラは、オーディオスレッドの中で、オーディオの1つのシグナルベクタが要する時間に等しい時間間隔で動作します。しかし、ノンリアルタイム・オーディオドライバが使用される場合、オーディオスレッドはメインスレッド内で動作します。そして、SIAI が使用可能(イネーブル)であれば、スケジューラもまたメインスレッド内で実行されます。そうでない場合には、スケジューラはオーバードライブ(Overdrive)の設定に基づいて、メインスレッド、あるいはタイマスレッド内で動作します。(SIAI なしでノンリアルタイム・オーディオドライバを使用することは、一般に、予測不可能な結果をもたらすことにつながるため、推奨できません。)

スレッド保護

スレッド保護を行なう最も簡単な方法は、クリティカルセクションを使用することです。クリティカルセクションとは、他のスレッドが割り込むことができないコード領域を表します。クリティカルセクションへ入る、あるいはクリティカルセクションから出るときには、これを宣言しますが、そのためには critical_enter() および critical_exit() を使用します。

Max はデフォルトのグローバルクリティカルセクションを提供しているため、これを使用することができます。この同じクリティカルセクションは、アウトレットなど数多くの共通したMax のデータ構造のために、タイマスレッドをメインスレッドから保護する(あるいは逆にメインスレッドをタイマスレッドから保護する)ために使用されます。 critical_enter()critical_exit() を、引数に 0 を与えて呼び出した場合、このグローバルクリティカルセクションを使用していることになります。一般的に、より少ないクリティカルセクションをを使用するほうが効果的です。したがって、様々な用途においてグローバルクリティカルセクションは十分なものであると言えます。クリティカルセクションが再帰的である点に注意して下さい。このため、すでに保護されているコードの内部でクリティカルセクションから出た場合でも、特に問題は起こりません。

メッセージが到着したとき

オブジェクトに送られたメッセージは、このオブジェクト("myobject")に送られた同じメッセージに割り込むことができます。例えば、次のようなパッチを考えてみて下さい。左側には左インレットに接続された bang を送信する button があり、右側には、同じインレットに接続された metro があります。

ユーザ がbang を送信する button をクリックすると、オブジェクトに対してメインスレッドでメッセージが送信されます。オーバードライブが使用可能(イネーブル)になっている場合、metro は オブジェクトに対してタイマスレッドで bang メッセージをを送信します。それぞれお互いに割り込むことができます。もし、割り込むことが許されないようなデータ構造の処理をオブジェクトが実行しているとしたら、スレッド保護を使用しなければなりません。

クリティカルセクションの実例

次に示すのは、グローバルクリティカルセクションを使用して、配列データ構造のためにスレッド保護を行なっている例です。配列からデータを読み出す array_read() 、および、同じ配列にデータを挿入する array_insert() という2つの操作があると仮定しています。この場合、書き込み処理に対して読み込み処理が割り込まないこと、また、その逆の割り込みが起きないことを保証しておきたいと考えるでしょう。

    long array_read(t_myobject *x, long index)
    {
        critical_enter(0);
        result = x->m_data[index];
        critical_exit(0);
        return result;
    }

一度クリティカルリージョンに入った後は、コードのすべての経路がクリティカルリージョンから出なければならないという点に注意して下さい。そうでないと、Max の他のスレッドは決して実行されません。

    long array_insert(t_myobject *x, long index, long value)
    {
        critical_enter(0);
        // 既存のデータの移動
        sysmem_copyptr(x->m_data + index, x->m_data + index + 1, (x->m_size - x->m_index) * sizeof(long));
        // 新規データの書き込み
        x->m_data[index] = value;
        critical_exit(0);
    }

Copyright © 2008, Cycling '74