2012年1月8日日曜日

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


はいQSemaphoreを使ったスレッド間のアクセス制御をやっていきます。
セマフォとは並列処理の実行環境において、排他区間を確保し、資源に同時アクセスできる上限を規定したい時に用いるもので、(wikiより)QSemaphoreもそういったときに使用されます。

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

(samplethread.h)
#include <QSemaphore>
#include <QThread>
#include <stdlib.h>

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

static QSemaphore freeBytes(BufferSize);//BufferSize(今回は4)のリソースを持つ
static QSemaphore usedBytes;//リソースは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)
    {
        freeBytes.acquire();//freeBytesのリソースを-1
        buffer[i % BufferSize] = "ACGT"[(uint)(rand()) % 4];//ACGTのいずれかの文字を格納
        usedBytes.release();//usedBytesのリソースを+1
    }
}

void Consumer::run()
{
    for (int i = 0; i < DataSize; ++i)
    {
        usedBytes.acquire();//usedBytesのリソースを-1
        strings += buffer[i % BufferSize];//文字の取り出し
        freeBytes.release();//freeBytesのリソースを+1
    }
}

ややこしいですね。私もセマフォはほとんど使ったことがありませんのでかなり書きにくかったです。Producerは書き込みで、書き込んだ分freeBytesのリソースを減らし、usedBytesのリソースを増やしています。Consumerは読み込み、読み込んだ分のuseBytesのリソースを減らし、freeBytesのリソースを増やしています。

ヘッダ部はProducerとConsumerの二つのクラスを記述しています。それぞれ書き込み用スレッド、読み込み用スレッドとなります。そして、この二つのクラスで共有する各定数や変数を定義しています。

さらにヘッダ部で二つのQSemaphoreを定義されています。freeBytesはどのくらい書き込めるかを、usedBytesはどのくらい読み込めるかを表します。
それで、このQSemaphoreをどんな感じに使うかですが

QSemaphore sem(5);      // 残りのリソース5
sem.acquire(3);         // 残りのリソース2
sem.acquire(2);         // 残りのリソース0
sem.release(5);         // 残りのリソース5
sem.release(5);         // 残りのリソース10

のような感じで使うそうです。一見意味不明ですので説明します。

まず1行目、5つのリソースを確保したQSemaphoreを定義しています。
2行目はacquire(3)を呼び出しリソースを3つ使用します。残りリソースは2となります。
3行目はactuire(2)を呼び出しリソースを2つ使用します。残りリソースは0となります。この時リソースは0となったのでリソースを確保できるまでsem.acquireを呼び出せません。ブロックされます。
4行目はsem.release(5)を呼び出しリソースを5つ開放しています。残りリソースは5となります。
5行目はさらにsem.release(5)を呼び出しリソースをさらに5開放しています。この場合、最初のリソースの大きさである5より大きいので、新しくsemのリソースが10としてcreateされます。(リファレンス

あとはコメントの通りです。
実行は以下のようになります。

(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に結果を表示しています。
実行すると以下のようになります。


以上です。