Qt3.2のマルチスレッドプログラミング


QtはC++ベースのプラットフォーム間GUIシステムとして,ユーザにグラフィックユーザインタフェースを構築する強力な機能を提供することができる.ユーザが複雑なグラフィックインタフェースシステムを構築するニーズを満たすために、Qtは豊富なマルチスレッドプログラミングサポートを提供している.2.2バージョンから、Qtは主に以下の3つの面からマルチスレッドプログラミングをサポートしています.1、プラットフォームに関係のない基本的なスレッドクラスを構築しました.二、ユーザーカスタムイベントを提出するThread-safe方式;三、信号量、グローバルロックなどの複数のスレッド間同期メカニズム.これらはユーザーに大きな便利さを提供しています.しかしながら、場合によっては、タイマメカニズムを用いることで、Qt自体のマルチスレッドメカニズムを用いるよりも必要な機能を容易に実現することができ、同時に不安全な現象の発生を回避することができる.本論文では,Qtにおけるマルチスレッドサポートメカニズムについて議論するだけでなく,タイマメカニズムを用いてマルチスレッドプログラミングをシミュレートする方法についても重点的に検討した.
1、マルチスレッドプログラミングに対するシステムのサポート
 
Qtに対する異なるプラットフォームのマルチスレッドサポート方式は異なる.ユーザーがWindowsオペレーティングシステムにQtシステムをインストールする場合、スレッドサポートはコンパイラのオプションであり、Qtのmkfilesサブディレクトリには異なる種類のコンパイラのコンパイラファイルが含まれており、-mt接尾辞付きのファイルこそマルチスレッドをサポートしています.
Unixオペレーティングシステムでは、configureスクリプトファイルを実行するときに-threadオプションを追加することで、スレッドのサポートが追加されます.インストールプロセスではlibqt-mtという独立したライブラリが作成されるため、マルチスレッドプログラミングをサポートするには、通常のQtライブラリ(-lqt)ではなく、ライブラリにリンクする必要があります(リンクオプションは-lqt-mt).
また、どのプラットフォームであっても、スレッドサポートを追加する際にマクロQT_を定義する必要があるTHREAD_SUPPORT(コンパイルオプション-DQT_THREAD_SUPPORT)を追加します.Windowsオペレーティングシステムでは、これは通常qconfigである.hファイルにオプションを追加して実現します.Unixシステムでは、関連するMakefileファイルに通常追加されます.
2、Qt中のスレッドクラス
 
Qtシステムにおいてスレッドに関連する最も重要なクラスはもちろんQThreadクラスであり、このクラスは新しいスレッドを作成し、スレッドの実行を制御する様々な方法を提供する.スレッドはQThread::run()リロード関数によって実行されます.これはJava言語のスレッドクラスに似ています.Qtシステムでは、ウィンドウシステムからイベントを取得し、各コンポーネントに配布して処理するGUIプライマリイベントスレッドが常に実行されている.QThreadクラスには、Qt内のThread-safeのイベントコミットプロセスを提供する非プライマリイベントスレッドからイベントをオブジェクトにコミットする方法、すなわちQThread::postEvent()メソッドもあります.コミットされたイベントがキューに入れられ、GUIプライマリ・イベント・スレッドが起動され、このイベントが対応するオブジェクトに送信されます.このプロセスは、一般的なウィンドウ・システムのイベント処理プロセスと同じです.なお、QThread::postEventメソッドを呼び出すスレッドではなく、イベント処理プロセスが呼び出されると、メインイベントスレッドで呼び出される.たとえば、あるスレッドから別のスレッドに指定した領域を再描画させることができます.


QWidget *mywidget;
QThread::postEvent(mywidget, new QPaintEvent(QRect(0,0,100,100)));


しかし、1つのスレッドクラスだけでは不十分であり、マルチスレッドをサポートするプログラムを作成するためには、共有データに対する2つの異なるスレッドの反発アクセスを実現する必要があるため、QtはQMutexクラスを提供し、1つのスレッドは臨界データにアクセスする際にロックを追加する必要があり、この場合他のスレッドはこの臨界データを同時にロックすることができない.前のスレッドが臨界データを解放するまで.このようにしてこそ,臨界データに対する原子操作を実現できる.
これに加えて、待機状態にあるスレッドが特定の場合に起動されるようにするメカニズムも必要である.QWaitConditionクラスはこの機能を提供しています.特定のイベントが発生すると、QWaitConditionは、イベントを待機しているすべてのスレッドを起動するか、選択したスレッドを起動します.
3、ユーザーカスタムイベントのマルチスレッドプログラミングにおける応用
 
Qtシステムでは、タイマイベント、マウス移動イベント、キーボードイベント、ウィンドウコントロールイベントなど、多くの種類のイベントが定義されています.通常、イベントは最下位のウィンドウシステムから来ており、Qtのプライマリイベントループ関数は、システムのイベントキューからこれらのイベントを取得し、それらをQEventに変換し、対応するQObjectsオブジェクトに渡す.
このほか、Qtシステムは、ユーザのニーズを満たすために、QThread::postEvent()またはQAPplication::postEvent()を使用して様々なコントロールまたは他のQObjectの実例に配布されるQCustomEventクラスを提供する.QWidgetクラスのサブクラスは、QWidget::customEvent()イベント処理関数によってこれらのカスタムイベントを容易に受信することができる.QCustomEventオブジェクトは、作成時にイベントタイプを定義するためにタイプIDを持っています.Qtシステムで定義されたイベントタイプと競合しないように、列挙タイプQEvent::Typeで指定された「User」値より大きい値を持つ必要があります.
次の例では、マルチスレッドプログラミングでユーザー定義イベントクラスをどのように利用するかを示します.
UserEventクラスは、346798として識別されたユーザー定義のイベントクラスであり、システム定義のイベントタイプと競合しないことは明らかです.


class UserEvent : public QCustomEvent //
{
public:
UserEvent(QString s) : QCustomEvent(346798), sz(s) { ; }
QString str() const { return sz; }
private:
QString sz;
};


UserThreadクラスはQThreadクラスから継承されたサブクラスであり、このクラスでは関連する変数とスレッド制御関数を定義するほか、スレッドを定義する起動関数UserThread::run()が最も主要であり、この関数にユーザカスタムイベントUserEventを作成し、QThreadクラスのpostEvent関数を利用して対応する受信オブジェクトにこのイベントをコミットする.


class UserThread : public QThread //
{
public:
UserThread(QObject *r, QMutex *m, QWaitCondition *c);
QObject *receiver;
}

void UserThread::run() // ,
{UserEvent *re = new UserEvent(resultstring);
QThread::postEvent(receiver, re);
}


UserWidgetクラスは、slotGo()関数を使用して新しいスレッドrecv(UserThreadクラス)を作成し、対応するカスタムイベント(idが346798)を受信したときにcustomEvent関数を使用してイベントを処理するカスタムイベントを受信するためのQWidgetクラスのサブクラスである.


void UserWidget::slotGo() //
{ mutex.lock();
if (! recv)
recv = new UserThread(this, &mutex, &condition);
recv->start();
mutex.unlock();
}

void UserWidget::customEvent(QCustomEvent *e) //
{ if (e->type()==346798)
{
UserEvent *re = (UserEvent *) e;
newstring = re->str();
}
}


この例では、UserWidgetオブジェクトに新たなスレッドUserThreadが作成され、ユーザはこのスレッドを利用して周期的な処理(下位層からのメッセージの受信など)を実現することができ、特定の条件を満たすとユーザがカスタマイズしたイベントをコミットし、UserWidgetオブジェクトがイベントを受信すると、必要に応じて対応する処理を行うことができるが、一般的には、UserWidgetオブジェクトは、下位メッセージの影響を全く受けずに、いくつかのルーチン処理を正常に実行できます.
4、タイマーメカニズムを利用してマルチスレッドプログラミングを実現する
 
Qtシステムにおけるマルチスレッドプログラミングによる問題を回避するために,システムに提供されるタイマメカニズムを用いて類似の機能を実現することもできる.タイマメカニズムは、同時イベントをシリアル化し、同時イベントの処理を簡素化し、thread-safeに関する問題の発生を回避する.
次の例では、下位層から送られてきたメッセージ(Socket、FIFOなどのプロセス間通信メカニズムを介してもよい)を受信する必要がある複数のオブジェクトが同時に存在し、メッセージはランダムに受信され、GUIプライマリスレッドがメッセージの受信を担当する必要がある.メッセージを受信すると、プライマリ・スレッドは対応するオブジェクトを初期化して処理を開始させ、同時に戻る.これにより、プライマリ・スレッドは常にインタフェースの表示を更新し、外部から送信されたメッセージを受信し、同時に複数のオブジェクトに対する制御を達成することができる.一方,各オブジェクトはメッセージ処理後にGUIマスタースレッドに通知する必要がある.この問題では、第3節のユーザがイベントをカスタマイズする方法で、メインスレッドにイベントフィルタをインストールして、各オブジェクトから送信されたカスタムイベントをキャプチャし、メインスレッドのスロット関数を呼び出す信号を送信することができます.
また、Thread-safeの問題を心配することなく、Qtのタイマメカニズムを用いて同様の機能を実現することもできる.次のコードセクションについて説明します.
ユーザ定義のサーバクラスでタイマを作成して起動し、connect関数を使用してタイマタイムアウトをデバイスファイルデータの読み取りに関連付けます.


Server:: Server(QWidget *parent) : QWidget(parent)
{
readTimer = new QTimer(this); //
connect(readTimer, SIGNAL(timeout()), this, SLOT(slotReadFile())); // slotReadFile
readTimer->start(100);
}


slotReadFile関数は、タイマのタイムアウト時にファイルからデータを読み出し、タイマを再起動します.


int Server::slotReadFile() //
{
readTimer->stop(); //
ret = read(file, buf ); //
if(ret == NULL)
{ readTimer->start(100); // ,
return(-1);
}
else
buf ……;
readTimer->start(100); //
}


このプログラムでは,ユーザが指定したデバイスファイルを同様にタイミングよく読み出し,読み出したデータ内容に基づいて各対応するオブジェクトに情報を送信する.ユーザは、自分のGUIプライマリスレッドにサーバクラスを作成し、最下位のメッセージ受信プロセスを実現するのに役立ち、インタフェース表示などの問題を処理することができます.各オブジェクトが処理を完了した後、タイマを再起動することによって、下位デバイスファイルを周期的に読み出すプロセスが継続される.もちろん、この方法は、各オブジェクトのイベントに対する処理時間が短く、下位デバイスからメッセージが送信される頻度が相対的に遅い場合に適している.この場合、上記の方法は、ユーザのニーズを完全に満たすことができ、スレッドの同時処理に関連する複雑な問題を処理することを回避することができる.
もちろん、タイマメカニズムを利用してマルチスレッドプログラミングを実現することはいくつかの面で一定の限界があり、マルチスレッドプログラミングを実現する方法、効率の高いコードをどのように作成するかについては、開発者のさらなる研究と検討が必要である.
参考資料
(1)Qt公式文書http://doc.trolltech.com/3.2/index.html
(2)Qtソースコード:QThread,QCustomEvent,QTimer.
(3)Advanced Programming in the UNIX Environment, W. Richard Stevens.