本家のオンラインマニュアル

スレッド

 ちょっと微妙なお話し「スレッド」です.

 GUIアプリの動作は,ユーザの操作(など)といったイベントを起点とするものが基本です. しかし,イベントによって始まった処理の時間が長くかかる場合はちょっと問題です. 例えば,Webブラウザを思い浮かべるとわかりやすいですが, サイズの大きなデータ(動画など)をダウンロードする場合,そのデータをダウンロードするまで時間がかかります. そのデータをダウンロードし終わるまで,他の一切の操作が出来ないとしたらとても不便です. 幸いにも実際のWebブラウザは, 1つの時間がかかる処理を実行しながらでも別の処理を実行することができるようになっています. これはどのような仕組みによるものでしょうか.

 プログラムというものは基本的に,開始から終了まで1つの流れであり, '{' から '}' まで停止することなく続きます. これを中断することなく別の処理を実行するための仕組みがマルチスレッディングで, 主要なプログラム言語は大抵マルチスレッディングの仕組みを提供しています. そして,別々に実行される処理をそれぞれスレッドといいます.

 wxWidgetsはマルチスレッディングを実現するための簡便な方法を提供しており, ここではそれについて説明します.


■ 時間のかかる処理の発動 (1)

 下のサンプルプログラム 'Thread1.cpp' は, ウィンドウ上のボタン「Start」をクリックすると長い計算処理 'Entry()' が始まり, それが終わるとウィンドウ上のテキストフィールドに "READY!" と表示するものです. このときEntry()の処理が終わるまで,ユーザからの一切の操作を受け付けなくなり, フリーズしたかのような状態になります. (もちろんEntry()の処理が終わればユーザからの操作を受け付ける状態になります)

 一度,このプログラムをコンパイルして実行し,厄介な状態を体験してみてください. それから後で,厄介な点を解決する別のサンプルプログラム 'Thread2.cpp' について説明します.

プログラム全体:Thread1.cpp
#include <wx/wx.h>

//-------------------------------------------------------
// Sub Routine
//-------------------------------------------------------
void Entry()
{
    long	i, j;
    double	s;

    s = 0.0;
    for ( i = 0; i < 50000; i++ ) {
        if ( i % 5000 == 0 ) {
            printf("i: %8ld\n",i);
        }
        for ( j = 0; j < 50000; j++ ) {
            s += rand();
        }
    }
    printf("%lf\n",s);
}

//-------------------------------------------------------
// Application Frame
//-------------------------------------------------------

//--- Class ---
class appFrameClass: public wxFrame {
  public:
    appFrameClass(int id, const wxString& title);
    
    virtual void On_mQuit(wxCommandEvent &event);
    virtual void On_btn(wxCommandEvent &event);
    
  protected:
    wxMenuBar* menubar;
    wxButton* btn;
    wxTextCtrl* tf;
    
    DECLARE_EVENT_TABLE();
};

//--- Constructor ---
appFrameClass::appFrameClass(int id, const wxString& title)
  :wxFrame(NULL, id, title)
{
    menubar = new wxMenuBar();
    wxMenu* mFile = new wxMenu();
    mFile->Append(10101, wxT("Quit"));
    menubar->Append(mFile, wxT("File"));
    SetMenuBar(menubar);
    btn = new wxButton(this, 10201, wxT("Start"));
    tf = new wxTextCtrl(this, 10301, wxT(""));

    SetTitle(title);
    SetSize(wxSize(200, 150));
    btn->SetMinSize(wxSize(180, 40));
    tf->SetMinSize(wxSize(180, 40));
    wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
    sizer->Add(btn,0,wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL,0);
    sizer->Add(tf,0,wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL,0);
    SetSizer(sizer);
    Layout();
}


//-------------------------------------------------------
// Event Handling
//-------------------------------------------------------
BEGIN_EVENT_TABLE(appFrameClass, wxFrame)
    EVT_MENU(10101, appFrameClass::On_mQuit)
    EVT_BUTTON(10201, appFrameClass::On_btn)
END_EVENT_TABLE();


//--- Menu: Quit ---
void appFrameClass::On_mQuit(wxCommandEvent &event)
{
    event.Skip();
    Close();
}

//--- Button ---
void appFrameClass::On_btn(wxCommandEvent &event)
{
    event.Skip();

    Entry();

    tf->SetValue(wxT("READY!"));
}

//-------------------------------------------------------
// Application
//-------------------------------------------------------

//--- Class ---
class MyApp: public wxApp {
  public:
    bool OnInit();
};

IMPLEMENT_APP(MyApp)

//--- Initialization ---
bool MyApp::OnInit()
{
    srand(time(0));
    appFrameClass* appFrame = new appFrameClass(10001, wxT("Thread"));
    SetTopWindow(appFrame);
    appFrame->Show();
    return true;
}


■ 時間のかかる処理の発動 (2)

 さて次はマルチスレッディングで問題を解決する形のプログラム 'Thread2.cpp' です. wxWidgetsでマルチスレッディングを実現するには 'wxThread' クラスを利用します.

プログラム全体:Thread2.cpp
#include <wx/wx.h>

//-------------------------------------------------------
// Thread
//-------------------------------------------------------

//--- Class ---
class MyThread : public wxThread {
  public:
    virtual void *Entry();
};

//--- Thread Routine ---
void *MyThread::Entry()
{
    long	i, j;
    double	s;

    s = 0.0;
    for ( i = 0; i < 50000; i++ ) {
        if ( i % 5000 == 0 ) {
            printf("i: %8ld\n",i);
        }
        for ( j = 0; j < 50000; j++ ) {
            s += rand();
        }
    }
    printf("%lf\n",s);

    return( NULL );
}

//-------------------------------------------------------
// Application Frame
//-------------------------------------------------------

//--- Class ---
class appFrameClass: public wxFrame {
  public:
    appFrameClass(int id, const wxString& title);
    
    virtual void On_mQuit(wxCommandEvent &event);
    virtual void On_btn(wxCommandEvent &event);
    
  protected:
    wxMenuBar* menubar;
    wxButton* btn;
    wxTextCtrl* tf;

    MyThread *th;
    
    DECLARE_EVENT_TABLE();
};

//--- Constructor ---
appFrameClass::appFrameClass(int id, const wxString& title)
  :wxFrame(NULL, id, title)
{
    menubar = new wxMenuBar();
    wxMenu* mFile = new wxMenu();
    mFile->Append(10101, wxT("Quit"));
    menubar->Append(mFile, wxT("File"));
    SetMenuBar(menubar);
    btn = new wxButton(this, 10201, wxT("Start"));
    tf = new wxTextCtrl(this, 10301, wxT(""));

    SetTitle(title);
    SetSize(wxSize(200, 150));
    btn->SetMinSize(wxSize(180, 40));
    tf->SetMinSize(wxSize(180, 40));
    wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
    sizer->Add(btn,0,wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL,0);
    sizer->Add(tf,0,wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL,0);
    SetSizer(sizer);
    Layout();

    th = new MyThread();
    th->Create();
}


//-------------------------------------------------------
// Event Handling
//-------------------------------------------------------

BEGIN_EVENT_TABLE(appFrameClass, wxFrame)
    EVT_MENU(10101, appFrameClass::On_mQuit)
    EVT_BUTTON(10201, appFrameClass::On_btn)
END_EVENT_TABLE();


//--- Menu: Quit ---
void appFrameClass::On_mQuit(wxCommandEvent &event)
{
    event.Skip();
    Close();
}

//--- Button ---
void appFrameClass::On_btn(wxCommandEvent &event)
{
    event.Skip();

    th->Run();

    tf->SetValue(wxT("READY!"));
}

//-------------------------------------------------------
// Application
//-------------------------------------------------------

//--- Class ---
class MyApp: public wxApp {
  public:
    bool OnInit();
};

IMPLEMENT_APP(MyApp)

//--- Initialization ---
bool MyApp::OnInit()
{
    srand(time(0));
    appFrameClass* appFrame = new appFrameClass(10001, wxT("Thread"));
    SetTopWindow(appFrame);
    appFrame->Show();
    return true;
}

 このプログラムではwxThreadクラスから派生するMyThreadクラスを定義しています. wxThread関数のメンバ関数としてEntry()を実装することで, それを別のスレッドとしてアプリ本体とは別の流れとして(同時並行で走る形で)実行することができます. ところで,このEntry()という関数名はwxThreadクラスで予約された名前なので, 独立したスレッドの処理を記述する場合はEntryという名前の関数として定義してください.

 上のプログラムでは,wxThreadの派生クラスMyThreadクラスとそのメンバ関数Entry()を定義したあと, アプリのウィンドウフレームのクラス(appFrame)のメンバとしてMyThreadクラスのインスタンス 'th' を宣言し,appFrameのコンストラクタの中でthを生成しています.

 スレッドのインスタンスに対してCreate()関数を実行したあとRun()関数を実行すると, 関数Entry()が独立したスレッドとして起動します. このプログラムのEntry()は終了するまでかなり時間がかかりますが,別のスレッドとして処理を続けるので, その終了を待たずにアプリはユーザからの操作を受け付ける状態になります.

実行の様子を一応下に示します.

         
Macintoshで実行した様子
Windowsで実行した様子


[ページトップへ]


2014/08/31