2012年1月9日月曜日

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


私は週5以上はランニングをしている。健康のためなのだがいかんせんランニングだけでは筋力が落ちるのを止められない。ランナーみたいなガリガリ体には正直なりたくない。そこでウエイトトレーニングでもまたやろうかとも思ったが、なんかプロテイン代とか時間がもったいない感が強く、踏み出せない。かといって自重トレーニングでは軽すぎて話にならない。そこではじめたのがサーキット自重トレーニング。これはいいです。全身が鍛えられてスタミナも付き、そんでもって、せいぜいかかっても40分という一般人には夢のメニューです。問題はかな~りキツイということですが...そこは気合です。


はい、それではQWaitConditionを使ったスレッド間のアクセス制御をやっていきます。
QWaitConditionはスレッドを同期させるための条件変数を提供するクラスです。(リファレンス)

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


(samplethread.h)
#include <QThread>
#include <stdlib.h>
#include <QWaitCondition>
#include <QMutex>

const static int DataSize = 25;//全データ量
const static int BufferSize = 4;//読み書きのバッファサイズ
static char buffer[BufferSize];

static QWaitCondition bufferIsNotFull;
static QWaitCondition bufferIsNotEmpty;
static QMutex mutex;
static int usedSpace = 0;//バッファスペースカウンタ

class Producer : public QThread
{
    Q_OBJECT
public:
    void run();//QThreadのrunの再実装
    
};

class Consumer : public QThread
{
    Q_OBJECT
public:
    void run();//QThreadのrunの再実装
    QString strings;//ここに出力用文字列が入る

};

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

void Producer::run()
{
    for (int i = 0; i < DataSize; ++i)
    {
        mutex.lock();
        while (usedSpace == BufferSize)//バッファが満杯かどうか
            bufferIsNotFull.wait(&mutex);//バッファが満杯じゃなくなるまで待つ
        buffer[i % BufferSize] = "ACGT"[uint(rand()) % 4];//ACGTのいずれかの文字を格納
        ++usedSpace;//バッファスペースカウンタを増やす
        bufferIsNotEmpty.wakeAll();//バッファが空以外という条件で待機してるものを起こす
        mutex.unlock();
    }
}

void Consumer::run()
{
    for (int i = 0; i < DataSize; ++i)
    {
        mutex.lock();
        while (usedSpace == 0)
            bufferIsNotEmpty.wait(&mutex);//バッファが空以外になるまで待つ
        strings += buffer[i % BufferSize];
        --usedSpace;//読み込んだのでバッファスペースカウンタを減らす
        bufferIsNotFull.wakeAll();//バッファが満杯以外の条件で待機中のものを起こす。
        mutex.unlock();
    }
}

はい前回よりややこしいですね。ヘッダ部はあまり前回と変わりありません。違うのはQWaitConditionやQMutex、usedSpaceを宣言しているくらいです。
.cpp部は前回と同じようにProducerのrun()が書き込み、Consumerのrun()が読み込みを担当しています。
ところどころでbufferIsNotEmpty.waitやbufferIsNotFull.wakeAllが呼ばれていますが、waitがスレッドを停止させ、wakeAllが停止されているスレッドを再開させるという意味をもちます。これらを入れ子のように使い。書込み→読みをスレッド間で同期を取りながら制限数まで繰り返しています。

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


(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);

    Producer producer;
    Consumer consumer;
    producer.start();//書込みスレッド開始
    consumer.start();//読み込みスレッド開始
    producer.wait();//書込みスレッドが終わるまで待つ
    consumer.wait();//読み込みスレッドが終わるまで待つ
    ui->label->setText(consumer.strings);
}

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

簡単ですね。コンストラクタで2つのスレッドを開始し、終了を待ち、QLabelに結果を表示しています。
実行すると以下のようになります。

 

以上です。