AMIGO FOR Win32


気軽にスレッド!ThreadWrapper

ThreadWrapper

 ThreadWrapperは、Javaのスレッドクラスをもとに設計した Win32 スレッド用APIをC++へラッピングしたものです。ここでは、まず、ThreadWrapperのサンプルコードを示して、使い方を理解してもらった上で、ThreadWrapperのソースコードを紹介したいと思います。
 サンプルコードには、
sleepを使った例と、スレッドの待ちと通知の機構である wait, notify の使った例を紹介しています。


ThreadWrapper の Sleep を使う

 まず、sleepを使った例を示します。とっても味気ない単純なコードですが、まずは簡単なところから・・・。一応、裏でスレッドが走っているよということを示すため、ユーザーのイベントがあったら、スレッド内で経過した時間を表示するようにしてみました。

ThreadWrapperの使い方(その1)
Sleep サンプルソースコード 実行例
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include "ThreadWrapper.h"
#include <iostream>

class CounterClass : public ThreadWrapper {
public:
    CounterClass();
    virtual ~CounterClass();
    void stop();
    int get();
protected:
    virtual void run();
private:
    bool run_;
    int n_;
};

CounterClass::CounterClass() : 
    run_(false), n_(0) { }
CounterClass::~CounterClass() { }

void CounterClass::run() {
    run_ = true;
    while (run_) {
        sleep(1000); // 単位はmillisecond
        ++n_;
    }
    std::cout << "Thread End" << std::endl;
}
void CounterClass::stop() {
    run_ = false;
}
int CounterClass::get() {
    return n_;
}

int main() {
    CounterClass counter;
    counter.start();

    std::string input("");
    while (true) {
        std::cin >> input;
        if (input == "end") {
            counter.stop();
            break;
        } else if (input == ".") {
            std::cout << "Thread time: " 
                << counter.get() 
                << "sec"
                << std::endl;
        }
    }
    counter.join() // スレッドが終了するのを待つ
}
■必要なソースコード■

ThreadWrapper.h、ThreadWrapper.cpp
CounterSample.cpp
// 左のコード



■お手軽コンパイル■

$ g++ *.cpp -o CounterSample.exe

必要なソースコードを全て同じディレクトリにいれてください。


■サンプルの実行■

$ ./CounterSample.exe
.
// "."を入力すると経過時間表示
Thread time: 1sec
.
Thread time: 4sec
.
Thread time: 8sec
.
Thread time: 11sec
.
Thread time: 14sec
.
Thread time: 15sec
end
// "end" と入力すると終了
Thread End

$

 注意事項としては、プログラムの最後に、スレッドが終わらないうちに、メインプロセス(main関数)が終了しないように、joinを使ってスレッドの終了を待つようにしてください。そうしないと、スレッドが中途半端な状態で終了してしまい、スレッドの処理内容によってはリソースリークを引き起こします。


wait, notify を使った通知機構使った例

 ThreadWrapper は、wait と notify の待ちと、通知機構も使えます。以下にサンプルコードを示します。このサンプルは、キューに、文字が入力されると、スレッドが通知を受け、その文字をキューから取り出して、エコーバックするというとっても単純なものです。(つまらなすぎる?)

ThreadWrapperの使い方(その2)
Wait、Notify サンプルソースコード 実行例
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include <iostream>
#include <string>
#include <list>
#include "ThreadWrapper.h"

class Queue;

class ThreadClass : public ThreadWrapper {
public:
    ThreadClassA(Queue* queue);
    virtual ~ThreadClass();
    void input(const std::string& str);
protected:
    virtual void run();
private:
    Queue* queue_;
};

class Queue {
public:
    Queue();
    ~Queue();
    void add(const std::string& str);
    void attach(ThreadWrapper* thread);
    std::string take();
private:
    std::list<std::string> queue_;
    ThreadWrapper* thread_;
    Synchronized sync_;
};


ThreadClass::ThreadClass(Queue* queue) : 
    queue_(queue) { }
ThreadClass::~ThreadClass() { }

void ThreadClass::run() {
    while (true) {
        std::string str = queue_->take();
        std::cout << "ECHO: " 
                  << str << std::endl;
        if (str == "end") break;
    }
    std::cout << "Thread End" << std::endl;
}

Queue::Queue() : thread_(NULL) { }
Queue::~Queue() { }

void Queue::attach(ThreadWrapper* thread) {
    thread_ = thread;
}

void Queue::add(const std::string& n) {
    sync_.lock();
    queue_.push_back(n);
    sync_.unlock();
    if (thread_ != NULL) {
        thread_->notify();
    }
}

std::string Queue::take() {
    sync_.lock();
    if (thread_ != NULL) {
        while (queue_.size() == 0)
            thread_->wait(sync_);
    }
    std::string str = queue_.front();
    queue_.pop_front();
    sync_.unlock();
    return str;
}

int main() {
    Queue queue;
    ThreadClass myThread(&queue);
    queue.attach(&myThread);

    myThread.start();
    std::string in("");
    while (true) {
        std::cin >> in;
        queue.add(in);
        if (in == "end") break;
    }
    myThread.join(); // スレッドが終了するのを待つ
}
■必要なソースコード■

ThreadWrapper.h、ThreadWrapper.cpp
WaitNotifySample.cpp
// 左のコード



■お手軽コンパイル■

$ g++ *.cpp -o WaitNotifySample.exe

必要なソースコードを全て同じディレクトリにいれてください。


■サンプルの実行■

$ ./WaitNotifySample.exe
hello
// 文字を入力
ECHO: hello
// スレッドが入力をエコーバック
how are you
ECHO: how
ECHO: are
ECHO: you
Im fine thank you
ECHO: Im
ECHO: fine
ECHO: thank
ECHO: you
bye!
ECHO: bye!
end
// "end" を入力してプログラム終了
ECHO: end
Thread End

$

 Queue::add 関数内の、wait を呼び出すところで、一見、無限ループをしていて、なんじゃこりゃと思う方もいるかと思いますが、キューへ文字列の入力があるまで、スレッドはサスペンドされますので、CPUを消費することがありません。したがって、処理としては、とっても効率的なものです。

 待ちと通知には、通常、同期設計が必要になりますが、さすがに、Java のような synchronized キーワードは使えませんので、同期は、必要な部分だけロックオブジェクトを使ってロックをかける形になります。ThreadWrapper パッケージでは、Java に合わせて、Synchronized というロック用のクラスを定義しています。(ん!?Synchronize が正解か?)

 待ちと通知の同期設計の注意すべき点として、wait 関数を呼ぶ while文には、while 内の条件が保障されるように必ずロックをかける必要があります。ただ、ロック した状態(sync.lock)で、 wait に入ってしまうと、ロックオブジェクトを握ったままスレッドが停止してしまうので、他の同期処理を必要とする処理が動けなくなります。(これが所謂、デッドロックですね)

 そこで、wait 関数には、ロックオブジェクトである Synchronized を引数として渡してやり、サスペンドする前に を開放します。その後、notify を受けて処理が復活した場合は、ロックオブジェクトを再度ロックし、一連の処理を開始するように、ThreadWrapper を設計しました。

 逆に、wait関数に Synchronized の引数に定義することで、wait するときは、必ず Syncronized を使って同期設計することが制約事項になります

 このあたりの内容の詳細については、
『プログラミング言語Java 第3版』 に詳しいので参照してください。待ちと通知の機構について分かりやすく解説しています。

 蛇足ですが、ThreadWrapper には、Java同様 interrupt() 関数を用意しています。

 しかし、ThreadWrapper の interrupt()関数も、Java同様、使用は推奨できません。joinを用いないで、プロセス終了させてしまう場合と同様に、スレッドが中途半端な状態で終了してしまうため、リソースリークが発生する可能性が高く、非常に危険だからです。※だったら定義するなって?! でも、揃っていないと気持ち悪いんですよぉ〜

ThreadWrapper のソースコード

 それでは、ソースコードを以下に示します。Wrapperというだけあって、結構あっさりとしたものです。ThreadWrapper と Synchronized はインターフェース用の抽象クラスにすると、Posix の pthread にも置き換えできるかも知れませんね。

□ ThreadWrapper (Win32 API)
ThreadWrapper.h
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#ifndef THREADWRAPPER_HEADER_GUARD__
#define THREADWRAPPER_HEADER_GUARD__

#include <windows.h>

class Synchronized {
public:
    Synchronized();
    virtual ~Synchronized();  
    virtual void lock() const;
    virtual void unlock() const;
protected:
    bool   bMutexExisting_;
    HANDLE hMutex_;
private:
    Synchronized(const Synchronized& other);
    Synchronized& operator=(const Synchronized& other);
};

extern "C" static void ThreadFactory(void* obj);

class ThreadWrapper {
public:
    ThreadWrapper();
    virtual ~ThreadWrapper();
    bool start();
    void interrupt();
    void notify() const;
    void wait(const Synchronized& sync) const;
    void sleep(int millisec) const;
    void join() const;
    int getThreadId() const;
protected:
    friend void ThreadFactory(void*);
    virtual void run() = 0;
private:
    HANDLE hThread_;
    DWORD  dwThreadId_;
private:
    ThreadWrapper(const ThreadWrapper& other);
    ThreadWrapper& operator=(const ThreadWrapper& other);
};

#endif // THREADWRAPPER_HEADER_GUARD__
ThreadWrapper.cpp
// Copyright(C) 2004 Yoshinori Oota All rights reserved.
#include <iostream>
#include <process.h>
#include "ThreadWrapper.h"

Synchronized::Synchronized() : hMutex_(NULL), bMutexExisting_(false) {
    hMutex_ = CreateMutex(NULL, false, NULL);
    if (hMutex_ == NULL) {
        std::cerr << "CreateMutex Error [errno] : " << GetLastError() << std::endl;
        CloseHandle(hMutex_);
    } else {
        bMutexExisting_ = true;
    }
}

Synchronized::~Synchronized() { 
    unlock();
    CloseHandle(hMutex_);
}

void Synchronized::lock() const {
    if (!bMutexExisting_) return;
    WaitForSingleObject(hMutex_, INFINITE);
}

void Synchronized::unlock() const {
    if (!bMutexExisting_) return;
    ReleaseMutex(hMutex_);
}


void ThreadFactory(void* obj) {
    ThreadWrapper* thread = (ThreadWrapper*)obj;
    if (thread != NULL) {
        thread->isActive_ = true;
        thread->run();
        thread->isActive_ = false;
    }
    ExitThread(0);
}

ThreadWrapper::ThreadWrapper() : isActive_(false), hThread_(NULL), dwThreadId_(0) { }
ThreadWrapper::~ThreadWrapper() {
    if (hThread_ == NULL) return;
    if (isActive_) {
        interrupt();  // リソースリークの危険!
    }
    CloseHandle(hThread_);
}

bool ThreadWrapper::start() {
    hThread_ = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFactory, this, 0, &dwThreadId_);
    if (hThread_ == NULL) {
        std::cout << "CreateThread Error [errno]: " << GetLastError() << std::endl;
    } 
    return hThread_ != NULL;
}

void ThreadWrapper::wait(const Synchronized& sync) const {
    if (hThread_ == NULL) return;
    sync.unlock();    // スレッドをサスペンドする前に、他の処理が走れるようにロック解除
    SuspendThread(hThread_);
    sync.lock();      // notify を受けたので、処理を継続!ロック獲得
}

void ThreadWrapper::notify() const {
    if (hThread_ == NULL) return;
    ResumeThread(hThread_);
}

void ThreadWrapper::sleep(int millisec) const {
    if (hThread_ == NULL) return;
    Sleep(millisec);
}

void ThreadWrapper::interrupt() {  // リソースリークの危険があるため使用は推奨されない
    if (hThread_ == NULL) return;
    if (TerminateThread(hThread_, 0) == FALSE) {
        std::cerr << "TerminateThread Error [errno]: " << GetLastError() << std::endl;
    } else {
        isActive_ = false;
    }
}

void ThreadWrapper::join() const {
    if (hThread_  == NULL) return;
    WaitForSingleObject(hThread_, INFINITE);
}

int ThreadWrapper::getThreadId() const {
    return (int)dwThreadId_;
}

 本来、スレッドの制御には、_beginthread ですとか、_endthread 関数を使うことが推奨されているようですが、gcc がそこまで対応していないのか、コンパイルできなかったので、とりあえず、Win32 API をそのまま使っています。まぁ、趣味のプログラミングであれば、そこまで、こだわる必要がないでしょう。

 とまぁ、その程度の考えて作った代物ですので、内容については、正直、自信がありません。もし、使うなら学習用にとどめてください。とは言うもののせっかくなら、完成度を上げたいという気持ちもほんの少しだけありますので、もし、お気づきの点などあればご連絡いただけるとありがたいです・・・


【変更履歴】

変更日付 変更内容 変更範囲
2004.01.12 初版  
2004.05.02 ヘッダーガードの綴りの間違いを修正 ThreadWrapper.h

ご意見、ご感想はこちらまで。

Copyright(C) 2004 Yoshinori Oota All rights reserved.

本ホームページのリンクは自由です。複製、転載される場合は、必ず著者までご連絡をください。