Lesson 20:通信二回目で通信以外?! スレッドやりましょ

 通信二回目です。が、いきなり今回は通信以外のことについてやります。

 前回は一回だけしか受信できないプログラムでした。ですが、実際の通信プログラムでは、絶えず受信できるような状態になっているのが普通です。
 ちょっと前回作成した受信部分を見てみましょう。
do
{
    tv.tv_sec=2;
    tv.tv_usec=0;

    FD_ZERO(&fds);
    FD_SET(nsock,&fds);

    select(32,&fds,NULL,NULL,&tv);
} while(FD_ISSET(nsock,&fds)==0);

rec=recv(nsock,buf,sizeof(buf),0);
if(rec<=0)
{
    closesocket(nsock);
    nsock=-1;
    strcpy(buf,"Disconnect");   
}
 これを単純に繰り返すようにすると、
do
{
    do
    {
        tv.tv_sec=2;
        tv.tv_usec=0;

        FD_ZERO(&fds);
        FD_SET(nsock,&fds);

        select(32,&fds,NULL,NULL,&tv);
    } while(FD_ISSET(nsock,&fds)==0);

} while((rec=recv(nsock,buf,sizeof(buf),0))<=0);

closesocket(nsock);
nsock=-1;
とすることができます。
 この方法だと、受信状態で他の処理を行なう場合に、select関数の待ち状態をできるだけ短くして、ループ内で処理するしかありません。これでは簡単な処理を行なうのも大変になってしまいます。
 そこで受信処理を並列処理にして、他の処理への影響を最小限にしたいと思います。

 並列処理といって一番先に頭に浮かぶのは、やはりマルチスレッドでしょう。簡単にスレッドを作成できるのは、BeOSの特徴の一つですからね。
 BeOSにとってスレッドは特別な技術ではありません。ウィンドウを作成する、それだけでもスレッドを作成したことになります。つまり、BeOSのプログラムでは、一つのウィンドウでの処理が他のウィンドウの処理を阻害することがないということです。
 では、受信処理も別のウィンドウを作成して、そこで行なうようにしてみましょうか? もちろん、それも可能ですが、あまりスマートな方法とは思えません。そこで、ウィンドウを作らずに別のスレッドを作って。そこで受信処理ができるようにしましょう。

 いきなり受信処理をやっても良いのですが、ここはまず、スレッドを作成して、その動きを確認してからにしましょう。
 スレッドの確認をするために、AltキーやCtrlキー等の特殊キーの状態を取得するmodifiers関数を使って、特殊キーの状態が変化したらそのことをウィンドウに通知するスレッドを作成して未ます。
 ウィンドウではストリングビューを用意しておき、特殊キーの状態が変化した回数をカウントアップして表示するようにしてみました。

 まず、スレッドの処理を記述する方法ですが、処理を記述するにはスレッド用の関数に記述します。スレッド用の関数は「int32 func(void *)」といった、void型のポインタを引数に持つint32型の任意の関数です。
 この関数をスレッドとするにはspawn_thread関数を使用します。

 spawn_thread関数は4つの引数を持ちます。
 1つ目の引数はスレッドとして使用する関数を指定します。2つ目はスレッドを識別するための名前を指定します。find_thread関数を使用することで、名前でスレッドを検索し、スレッドのIDを取得することができます。
 3つめの引数は、スレッドの優先度を指定します。優先度は100未満の値と、100以上の値で意味が違ってきます。100未満の値を指定した場合は、そのスレッドはタイムシェアリングで実施されます。タイムシェアリングは待ちキューにリアルタイムスレッドが存在しない場合に実行されます。3ミリ秒単位で一つのスレッドが実行されますが、この際、大きい値を指定されているスレッドほど実行される可能性が高くなります。
 100以上の値を指定した場合は、そのスレッドはリアルタイムスレッドとして実施されます。リアルタイムスレッドは、すぐさまスレッドの実行が行なわれますが、リアルタイムスレッドが複数存在する場合は、値の大きいスレッドを先に実行します。
 スレッドの優先度としては、タイムシェアリングで4つの定数(B_LOW_PRIORITY、B_NORMAL_PRIORITY、B_DISPLAY_PRIORITY、B_URGENT_DISPLAY_PRIORITY)、リアルタイムで3つの定数(B_REAL_TIME_DISPLAY_PRIORITY、B_URGENT_PRIORITY、B_REAL_TIME_PRIORITY)が用意されていますので、通常は、その定数を使用します。
 4つめの引数は、スレッドの関数に渡すvoid型のポインタです。

 スレッド間のデータのやり取りには、send_data関数とreceive_data関数の組み合わせを使うなど、いくつかの方法があるのですが、今回は単純なメッセージを使用した方法にしたいと思います。
 ウィンドウに通知するメッセージをMSG_SENDとして、関数名をtest_threadとして、特殊キーの状態が変化したことをウィンドウに通知する関数を作ると、次のようになります。

//---------------------------------------------------------------------
int32 test_thread(void *Owner)
{
    uint32 oldValue,newValue;

    oldValue=modifiers();
    while(true)
    {
        if((newValue=modifiers())!=oldValue)
        {
            ((BWindow *)Owner)->PostMessage(new BMessage(MSG_SEND));
            oldValue=newValue;
        }
    }       
}
//---------------------------------------------------------------------

 引数のOwnerには呼び出し元のウィンドウのポインタをセットすることにします。
 modifiers関数は特殊キーの状態をint32型で返しますので、それを保存しておくoldValueと、現在の状態を格納するためのnewValueを用意しておきます。
 あとはwhile(true)で無限ループを作りだし、その中で特殊キーの状態に変化があったかを監視し、変化があった場合は、OwnerウィンドウにMSG_SENDメッセージを発行します。

 スレッドを実行するには、spawn_thread関数の戻り値から取得したスレッドのIDを使用します。wait_for_thread関数やresume_thread関数にIDを渡して実行します。今回はresume_thread関数を使用しますが、wait_for_thread関数だとどのような動きをするかを確認するのもやったほうが良いでしょう。

 それではスレッドの実行部分も含めてプログラム全体をみてみましょう。

/**** ファイル名 : MainWindow.h ****/

#ifndef MAINWINDOW
#define MAINWINDOW
//---------------------------------------------------------------------
#include <Be.h>
//---------------------------------------------------------------------
#define MAINWINDOW_TITLE            "Thread"
#define MAINWINDOW_POSITION_LEFT    100
#define MAINWINDOW_POSITION_TOP     100
#define MAINWINDOW_POSITION_WIDTH   400
#define MAINWINDOW_POSITION_HEIGHT  128
#define MAINWINDOW_WINDOWSTYLE      (B_TITLED_WINDOW)
//---------------------------------------------------------------------
#define MSG_SEND    'sndb'
//---------------------------------------------------------------------
class BAppMainView : public BView
{
public:
    BStringView *strview;
    BAppMainView(BRect frame);
};
//---------------------------------------------------------------------
class BAppMainWindow : public BWindow
{
private:
    int cnt;
    thread_id tstthr;
public:
    BAppMainView *mainview;
    //-----------------------------------------------------------------
    BAppMainWindow(BRect frame,const char *title);
    virtual void MessageReceived(BMessage *msg);
    //-----------------------------------------------------------------
    bool QuitRequested();
};
//---------------------------------------------------------------------
#endif

/**** ファイル名 : MainWindow.cpp ****/

//---------------------------------------------------------------------
#include "MainWindow.h"
//---------------------------------------------------------------------
int32 test_thread(void *Owner)
{
    uint32 oldValue,newValue;

    oldValue=modifiers();
    while(true)
    {
        if((newValue=modifiers())!=oldValue)
        {
            ((BWindow *)Owner)->PostMessage(new BMessage(MSG_SEND));
            oldValue=newValue;
        }
    }       
}
//---------------------------------------------------------------------
BAppMainWindow::BAppMainWindow(BRect frame,const char *title)
    :BWindow(frame,title,MAINWINDOW_WINDOWSTYLE,0)
{
    mainview=new BAppMainView(Bounds());    
    AddChild(mainview);

    cnt=0;
    tstthr=spawn_thread(test_thread,"TestThread",
                        B_NORMAL_PRIORITY,(void *)this);
    resume_thread(tstthr); 
}
//---------------------------------------------------------------------
void BAppMainWindow::MessageReceived(BMessage *msg)
{
    switch(msg->what)
    {
        case MSG_SEND:
            {
                char buf[100];
                
                cnt++;
                sprintf(buf,"cnt=%d",cnt);
                mainview->strview->SetText(buf);
            }
            break;
        default:
            BWindow::MessageReceived(msg);
    }
}
//---------------------------------------------------------------------
BAppMainView::BAppMainView(BRect frame)
    :BView(frame,"bappmainview",B_FOLLOW_ALL,B_WILL_DRAW)
{
    BRect viewrect(Bounds());
    strview=new BStringView(BRect(8,68,viewrect.right-8,88),"strview",
                "NoData",
                B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
    AddChild(strview);
}
//---------------------------------------------------------------------
bool BAppMainWindow::QuitRequested()
{
    kill_thread(tstthr); 
    be_app->PostMessage(B_QUIT_REQUESTED);
    return true;
};
//---------------------------------------------------------------------

 BAppMainWindowクラスのプライベートメンバとしてint cntとthread_id tstthrを宣言しています。cntは、特殊キーの状態が変化した回数をカウントアップしていくためのカウンタとして、tstthrはスレッドのIDを格納するために使用します。

 test_thread関数については上で触れましたので、スレッドの開始と、メッセージ受信、スレッドの終了について触れておきます。
 スレッドの準備と開始はBAppMainWindowクラスのコンストラクタで行っています。
 spawn_thread関数でtest_thread関数を別のスレッドにし、tstthr変数にスレッドのIDを格納します。
 作成されたばかりのスレッドはサスペンド状態なので、続いて行なっているresume_thread関数でサスペンド状態から再起(最初なので開始?)します。逆にサスペンド状態にするにはsuspend_thread関数を使用します。
 これでtest_thread関数は独立した一つのスレッドとして動作するようになります。

 メッセージの受信部分は、cntをインクリメントしてストリングビューに表示しているだけですから、説明の必要はありませんよね。
 最後にスレッドの終了を行なう部分ですが、QuitRequested関数内で行なっている「kill_thread(tstthr)」がその部分です。
 見たまんまの機能で、指定したスレッドを殺しています。
 本来、スレッドは、そのスレッド関数を終了すれば終わりなのですが、kill_thread関数を使用することで、外部からスレッドを終了することもできます。今回はメモリ解放等を考慮する必要が無かったので、kill_thread関数を使用しました。

   最後にプログラムを実行して動きをみてください。ShiftキーやAltキー、Ctrlキー等を押すとカウンタが1増え、話すと更に1増えているはずです。test_thread関数が動作していることを確認できたら今回は終わりです。
 あ、そうそう、ひとつ書き忘れていたことが・・・。このPulse等のプログラムを動かしておいて、このプログラムを動かすと、CPUの使用率が常に100%に近い状態になります。これは、test_thread関数が休む間もなく動き続けているからです。本来はそういったところまで考慮して作るべきなのですが、今回はあくまで練習ですので、そこまでは考慮しませんでした。
 次は、実際に受信処理を独立したスレッドにする部分をやってみたいと思います。

ソースリスト
圧縮ファイル
R5 Intel環境で確認
Be7thApp20001009.zip
ソースファイル BaseApp.h
main.cpp
MainWindow.cpp
MainWindow.h

次の項目へ

トップページへ戻る