2012年1月7日土曜日

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


最近PHPをやっているのだが、慣れていないので非常にやりずらい。あの$nameとかの変数に何でも入れていいところはなんていうか気持ちが悪い。int nameのような感じで慣れているので あれっ?型なんだったっけ?ああ気にしなくていいんだ!...みたいになる。これも慣れなんだろうな...。

はいそれではQReadWriteLockを使ったスレッド間のアクセス制御をやっていきます。
前回までのQMutexやQMutexLockerを使ったアクセス制御は単一スレッドからのアクセスしか許されません。
そこで複数スレッドからの同時アクセス(読込)が必要な場合などはQReadWriteLockをつかいます。
サンプルは独自のものを使います。使用サンプルは複数スレッドからの同時アクセスではないので本当は別にQReadWriteLockを使うメリットはありません。あくまでQReadWriteLockの使用例としてみてください。

そして、いつものようにQtCreaterの使用を前提とします。(QtCreaterなどの使い方は ”Qtをはじめよう" を見てください。) 

ではコードを

(custumthread.h)
#include <QObject>
#include <QThread>
#include <QReadWriteLock>

class CustumThread : public QThread
{
    Q_OBJECT//マクロ
public:
    explicit CustumThread();//コンストラクタ
    void stop();//スレッド停止
    QString getMessage();//messageStrの文字列を返す。

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

private:
    QString messageStr;//スレッドが走っているかの状態メッセージ格納
    volatile bool stopped;//volatileは処理系の最適化の抑制の意味
    QReadWriteLock lock;//←ここ重要    
};

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

CustumThread::CustumThread()
{
    messageStr = "Thread Stop!";
    stopped = false;
}

void CustumThread::run()//QThreadのrunの再実装
{
    bool first = true;

    forever
    {
        {
            if(stopped)
            {
                lock.lockForWrite();//書込みロック開始
                stopped = false;
                lock.unlock();//書込みロック解除
                break;
            }

            if(first)
            {
                lock.lockForWrite();//書込みロック開始
                messageStr = "Thread Running!";
                lock.unlock();//書込みロック解除
                first = false;
            }
        }
    }
    lock.lockForWrite();//書込みロック開始
    messageStr = "Thread Stop!";
    lock.unlock();//書込みロック解除
}

void CustumThread::stop()
{
    lock.lockForWrite();//書込みロック開始
    stopped = true;
    lock.unlock();//書込みロック解除
}

QString CustumThread::getMessage()
{
    lock.lockForRead();//読み込みロック開始
    QString temp = messageStr;
    lock.unlock();//読み込みロック解除

    return temp;
}


はい長いですね。ヘッダ部では新たにgetMessage()と QReadWriteLock lockを追加し、messageStrをprivateにしています。あとは前回と同じです。.cppではrun(),stop(),getMessage()の部分で QReadWriteLockを使用しています。これらのlockForWrite()とlockForRead()の2種類が使用されていますが、これはどういう意味かといいますと

lockForWrite()
書込みロック開始。もし、別スレッドが読み込みロックか書込みロックをすでに開始している場合は、現スレッドはブロックされる。
lockForRead()
読み込みロック開始。もし、自分もしくは他のスレッドが書き込みロックをすでに開始している場合には、現スレッドはブロックされる。

ということだそうです。(リファレンス
つまりlockForWrite()を開始するとどのスレッドからのlockForWrite()とlockForRead()を拒否でき、unlockが呼ばれるまで別スレッドのlockForWrite()とlockForRead()を待たせることができます。
lockForRead()の場合はlockForWrite()を呼び出そうとしたスレッドのアクセスを拒否でき、unlockが呼ばれるまで別スレッドのlockForWrite()を待たせることができます。

これらを実行するコードは以下のようになります。


(mainwindow.h)
#include <QMainWindow>
#include "custumthread.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)
#include "mainwindow.h"
#include "ui_mainwindow.h"

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.getMessage());//現在のスレッドの状態をラベルに表示
}

ヘッダ部は前回とまったく同じです。.cpp部ではpushButton_3_clickedのthreadA.getMessage()に変更しているだけであとは前々回と同じなので説明は省略します。 実行結果ももちろん前回と同じです。


以上です。