CSim::Schedule(時刻,オブジェクトのポインタ)
としている。
さて、これをライブラリではなく言語でサポートする場合、
どうあるべきだろうか?
汎用シミュレーション言語として売られているものでは、
上と同じように、事象のスケジュールを登録するものが多い。
しかし、より自然にプログラミングできるようにするためには、
指定された(シミュレーション上の)時間だけ待つ
という機能があった方がよい。
何故なら、このようにできるとプログラムが分断されなくて済み、
見通しが非常によくなるからだ。
例えば、待ち行列を例にとって見よう。
void 到着()
{
if (++バッファサイズ == 1) {
while (バッファサイズ > 0) {
Sleep サービス時間
--バッファサイズ
}
}
}
|
ここで Sleep が、「指定時間待つ」というコマンドである。言語の機能であり、関数ではない。
また、「時間」は UNIX の usleep のような現実の時間ではなく、シミュレーション上の時間である。
while は不必要と思うかもしれない。いやいや、必要なのだ。
つまり、Sleep で別の関数に飛ぶことがあり、戻った時にバッファサイズが増えているかもしれないのだ。
Sleep を用いないと、Sleep の前後で、関数を分けなければならなくなる。
実際、私のサンプルでは、到着事象とサービス終了事象を別の関数にしている。
この Sleep を使うことによって、驚くほどプログラムがシンプルになり見通しがよくなる
ことが想像できるだろうか。現在使われている主なプログラミング言語で、
このような機能を備えているものは見当たらない。取りあえず、実行してみるつもりならば
UNIX で関数毎に fork をし、usleep or sleep を使ってリアルタイムシミュレーションという方法が
考えられる。(Windows系ならばスレッドが使える。)
どこかで、こういうことができるコンパイラを開発してくれないものだろうか?
不完全ではあるが、上記の機能を C++ で実装することができる。 以下にサンプルを示す。
// sim.h
#include <iostream>
#include <map>
using namespace std;
#pragma warning(disable : 4786)
// 登録されるものの抽象クラス
// 派生して Callback をオーバーライトする
class CEve {
protected:
int m_startLabel; // 開始ラベル
public:
CEve(){m_startLabel = 0;}
virtual void Callback() = 0; // スケージュールされて呼ばれる
void Callback(int i){m_startLabel = i; Callback(); m_startLabel = 0;}
};
// イベント管理クラス
class CSim {
typedef multimap<double,pair<CEve*,int> > MType; // スタックの型
static MType m_stack; // イベント管理スタック
static double m_curTime; // 現在時刻
public:
static void Schedule(double t,CEve* p,int i = 0) // スケジュールする
{m_stack.insert(make_pair(t+m_curTime,make_pair(p,i)));}
static void Start(double simTime); // 実行開始
static double Now(){return m_curTime;} // 現在時刻
};
#define SLEEP(t) {CSim::Schedule(t,this,__LINE__); return; case __LINE__: ;}
#define CB_BEGIN switch (m_startLabel) { default:
#define CB_END }
// sim.cpp
//#include "sim.h"
CSim::MType CSim::m_stack;
double CSim::m_curTime = 0;
void CSim::Start(double simTime)
{
m_curTime = 0;
while (m_curTime < simTime && !m_stack.empty()) {
MType::iterator it = m_stack.begin();
m_curTime = it->first;
it->second.first->Callback(it->second.second);
m_stack.erase(it);
}
}
// mymain.cpp
//#include "sim.h"
// 待ち行列
class CQueue : public CEve {
int m_n; // 系内人数
public:
CQueue(){m_n = 0;}
virtual void Callback(); // 待ち行列に入る
};
// 到着管理
class CArrival : public CEve {
CQueue queue; // 待ち行列
public:
virtual void Callback(); // 到着開始
};
void CArrival::Callback()
{
CB_BEGIN
while (CSim::Now() < 16) {
SLEEP(3)
cout << CSim::Now() << ": arrive\n";
queue.Callback();
}
CB_END
}
void CQueue::Callback()
{
CB_BEGIN
if (++m_n == 1) while (m_n) {
SLEEP(5)
cout << CSim::Now() << ": finish\n";
--m_n;
}
CB_END
}
int main()
{
CArrival a;
CSim::Schedule(0,&a); // 到着開始をスケジュール
CSim::Start(100); // シミュレーション開始
return 0;
}
|
この例では、3分毎に5回到着が起こり、5分毎にサービスが終了する。
このように一応できるが、いろいろ制約もある。 例えば、値を保持したい変数は、メンバ変数にしないといけない。 このような制約を考えると、分割されても SLEEP を使わない方がよいであろう。
追記:
プログラミング言語 C++(第3版)の 12章の練習問題に「イベント駆動シミュレーションを設計、実装せよ」
というものがあった。その中で、「仮想時間を消費する task::delay(long)関数」というのが出てくる。
これは、私の提案している Sleep と同じ概念と思われる。しかし、関数で SLEEP マクロと同等の
機能を実装する方法は、ちょっと思い付かない。また、この練習問題に出てくる「<task.h>」という
ヘッダは何を指しているかよくわからない。
Stroustrup はシミュレーションをするために、C++ を開発した。そこで、delay のような概念を
思い付いているのだから、シミュレーション言語として組み入れ欲しかったものだ。