2012年1月4日水曜日

( Qt C++ )QMutexを使ったスレッド間のアクセス制御


はいそれでは表題の件やっていきます。
前回はQThreadのサブクラス化によるマルチスレッドをやりましたが、前回のやり方だけでは現実では危険です。なぜなら別のスレッドからのアクセスがあった場合を考慮していないからです。
(例えば複数スレッドから一つの変数にアクセスし変更する場合などは変数の整合性が各スレッドで保てなくなる危険性があります。)

そこで一つのスレッドからしかアクセスできないようにQMutexを使います。(リファレンス)これはざっくりいうとロックを開始すると、アンロックが呼ばれるまで変数を保護するというものです。アンロックが呼ばれるまで、ロックを開始したスレッド以外は変数にアクセスできなくなります。
(しかしながら一つのスレッドからしかアクセスできないので、多くのスレッドからのアクセス(読込)が必要な場合などには処理が遅くなるので別の方法が必要となります。このあたりは後ほど記事にします。)

サンプルは前回と同じものを使います。
そして、いつものようにQtCreaterの使用を前提とします。(QtCreaterなどの使い方は ”Qtをはじめよう" を見てください。)
なお、サンプルコードはincludeの部分は省略しております。動かない場合はまずinclude部を疑ってください。

ではコードを


(custumthread.h)
class CustumThread : public QThread
{
    Q_OBJECT//マクロ
public:
    explicit CustumThread();//コンストラクタ
    void stop();//スレッドを止める
    QString messageStr;//スレッドが走っているかの状態メッセージ格納

protected:
    void run();//スレッドを始める関数(再実装)

private:
    QMutex mutex;//<-----------------ここ重要!!
    volatile bool stopped;//volatileは処理系の最適化の抑制の意味
};


(custumthread.cpp)
CustumThread::CustumThread()//コンストラクタ
{
    messageStr = "Thread Stop!";
    stopped = false;
}
 
void CustumThread::run()//QThreadのrunの再実装
{
    bool first = true;
    forever
    {
        mutex.lock();//別スレッドからのアクセスロック
        if(stopped)//stoppedフラグが立っていたらループを抜ける
        {
            stopped = false;
            mutex.unlock();//ロック解除
            break;
        }
        mutex.unlock();

        if(first)//ループの最初の一回だけmessageStrを変更するという意味
        {
            mutex.lock();//別スレッドからのアクセスロック
            messageStr = "Thread Running!";
            first = false;
            mutex.unlock();//ロック解除
        }
    }
    mutex.lock();//別スレッドからのアクセスロック
    messageStr = "Thread Stop!";
    mutex.unlock();//ロック解除
}

void CustumThread::stop()
{
    mutex.lock();//別スレッドからのアクセスロック
    stopped = true;
    mutex.unlock();//ロック解除
} 


はい、簡単ですね。ヘッダはメンバにQMutex mutexが追加されたこと。.cppではrun()とstop()でmutex.lock(),mutex.unlock()を使いメンバ変数を保護していること。これら以外は前回と変わりません。
これらの実行は前回と同じように



(mainwindow.h)
namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT//マクロ
   
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
   
private slots:
    void on_pushButton_clicked();//スレッド開始ボタン

    void on_pushButton_2_clicked();//スレッド停止ボタン

    void on_pushButton_3_clicked();//スレッドの状態表示ボタン

private:
    Ui::MainWindow *ui;//uiにはGUI部品類が記述されている。
    CustumThread threadA;//<-----------ここ重要。
};


(mainwindow.cpp)
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)//コンストラクタ
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()//デストラクタ
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()//Startボタンクリック
{
    if(!threadA.isRunning())//スレッドが停止しているなら開始
   {
        threadA.start();
    }
}

void MainWindow::on_pushButton_2_clicked()//Stopボタンクリック
{
    if(threadA.isRunning())//スレッドが走ってるなら停止
   {
        threadA.stop();
        threadA.wait();//終わるまで待つ
   }
}

void MainWindow::on_pushButton_3_clicked()//状態表示ボタンクリック
{
    ui->label->setText(threadA.messageStr);//現在のスレッドの状態をラベルに表示
}


のようにします。実行部の説明は前回と同じなので省略します。 実行結果ももちろん前回と同じです。
 

以上です。