2012年1月11日水曜日

( Qt C++ )QThreadStorageを使いスレッド固有の値を格納する


はい、それではQThreadStorageを使いスレッド固有の値を格納するをやっていきます。
今までは各スレッドが同じ変数を参照して処理を行ってきました。しかし、スレッド別に固有の値、変数を持ちたいときがあろうかと思います。そこで役に立つのがQThreadStorageです。(リファレンス

サンプルはC++ GUI Programming with Qt4の391ページのものを少し変更して使用します。そして、いつものようにQtCreaterの使用を前提とします。(QtCreaterなどの使い方は ”Qtをはじめよう" を見てください。)
ではさっそくコードを

(samplestorage.h)
#include <QObject>
#include <QThread>
#include <QThreadStorage>
#include <QHash>

class SampleStorage : public QThread
{
    Q_OBJECT//マクロ
public:
    explicit SampleStorage(int id, QString value);//コンストラクタ
    ~SampleStorage();//デストラクタ
    void run();//QThreadのrunの再実装
    void cacheRemove(int id);//cacheの値の削除(今回は未使用)
    QThreadStorage<QHash<int, QString> *> cache;//スレッド固有の値を保持する
};


(samplestorage.cpp)
#include "samplestorage.h"

SampleStorage::SampleStorage(int id, QString value)
{
    if(!cache.hasLocalData())//cacheに現スレッドの固有のデータがあるか?
        cache.setLocalData(new QHash<int, QString>);//ないなら作る
    cache.localData()->insert(id, value);//登録
}

SampleStorage::~SampleStorage()
{
}

void SampleStorage::run()
{
    return;
}

void SampleStorage::cacheRemove(int id)//今回は未使用
{
    if(cache.hasLocalData())//現スレッド固有データが存在するか?
        cache.localData()->remove(id);あるなら指定idのものを消す
}

はい、簡単ですね。ヘッダ部はコメントの通りです。なおcacheはコンパイラによって問題がでるそうなのでポインタ宣言するそうです。
.cppはさらに簡単ですね。コンストラクタ部でcacheにスレッド固有のデータを登録しています。run()部は今回どうでもいいのですぐに処理を返しています。今回は使いませんでしたがcacheRemoveで現スレッドの固有データを削除することができます。

実行は以下のようになります。


(mainwindow.h)
#include <QMainWindow>
#include "samplestorage.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT//マクロ
    
public:
    explicit MainWindow(QWidget *parent = 0);//コンストラクタ
    ~MainWindow();//デストラクタ
private:
    Ui::MainWindow *ui;//GUI部品の記述。
    SampleStorage *sampStorage1;
    SampleStorage *sampStorage2;
};

(mainwindow.cpp)
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "samplethread.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)//コンストラクタ ui部にはGUI部品について書かれています。
{
    ui->setupUi(this);

    sampStorage1 = new SampleStorage(1, "thread1");//1, "thread1"で登録
    sampStorage2 = new SampleStorage(2, "thread2");//2, "thread2"で登録
    sampStorage1->start();//スレッド開始
    sampStorage2->start();
    sampStorage1->wait();//スレッド終了待ち
    sampStorage2->wait();

    ui->label->setText(sampStorage1->cache.localData()->value(1)
                       + "  " + sampStorage2->cache.localData()->value(2));//ラベルにスレッドスレッド固有データを表示
}

MainWindow::~MainWindow()
{
    delete ui;
}

はい、割と簡単ですね。ヘッダ部はコメントの通りです。.cpp部では重要なのはコンストラクタだけです。スレッドの実体を作る際、そのスレッド固有のデータとなる値を引数にします。その後、スレッドを開始し、終了後に各スレッド固有データをQLabelに表示しています。

※なお、固有データなので当たり前ですがsampStorage1がsampStorage2のcache.localData()を参照することはできません。ですので上記のサンプルで

sampStorage1->cache.localData()->value(2);

なんて書いてもsampStorage2のlocalDataの"thread2"は返ってきません。
それと、QThreadStorageはQThreadを開始したスレッドでのみしか使えないようです。(リファレンス下記に書かれています。)

実行すると以下のようになります。


以上です。