イベント
イベント(sc_event)はスレッド間の同期をとるために非常に役立ちます。
ここでは、イベント(sc_event)の使い方についてメモします。
ここでは、イベント(sc_event)の使い方についてメモします。
sc_eventの使い方
簡単なsc_event使用例
以下にもっとも簡単なsc_eventの使い方を記述します。
#include <systemc.h>
SC_MODULE( TOP )
{
sc_event e0; // イベント準備
SC_CTOR( TOP ) {
SC_THREAD( thread0 );
SC_THREAD( thread1 );
}
// イベントを通知するスレッド
void thread0() {
cout << "--- thread0 start ---" << endl;
while (true) {
wait( 10, SC_NS );
e0.notify(); // イベント通知
}
}
// イベントを受け取るスレッド
void thread1() {
cout << "--- thread1 start ---" << endl;
while (true) {
wait( e0 ); // イベント待ち
cout << sc_time_stamp() << " : thread1" << endl;
}
}
};
int sc_main( int argc, char* argv[] )
{
TOP top( "top" );
sc_start( 100, SC_NS );
return 0;
}
- 実行結果
--- thread0 start --- --- thread1 start --- 10 ns : thread1 20 ns : thread1 30 ns : thread1 40 ns : thread1 50 ns : thread1 60 ns : thread1 70 ns : thread1 80 ns : thread1 90 ns : thread1
とりあえずsc_eventの動作確認するだけなので、なんの面白みもないです。
あえて言うなら、こんな処理で2つスレッドを用意するのは無駄です。
説明のためにあえて2つのスレッドを用意しました。
あえて言うなら、こんな処理で2つスレッドを用意するのは無駄です。
説明のためにあえて2つのスレッドを用意しました。
イベントの用途は、基本はプロセス間の同期です。
記述を見ていきます。
まず、TOPモジュールのメンバ変数として、イベントe0を用意しています。
まず、TOPモジュールのメンバ変数として、イベントe0を用意しています。
sc_evnet e0;
thread0とthread1はSC_THREAD指定してwhile(true)文により無限ループとなっています。
thread0では、イベントを通知(発行)しています。
thread0では、イベントを通知(発行)しています。
e0.notify();
thread1では、イベントを待っています。
wait( e0 );
イベントを受け取ると、ウェイト状態が解除され、waitの後述部分が処理されます。
後述は、ここでは単に標準出力(cout)させてます。
後述は、ここでは単に標準出力(cout)させてます。
ようは、thread1の動作開始をthread0から操作しているようなイメージです。
今回はnotify()の引数はなにも与えていませんが、時間を指定することもできます。
たとえば、次のように記述できます。
たとえば、次のように記述できます。
e0.notify( 5, SC_NS );
このように記述すると、notifyが実行されてから5[ns]後にイベントが通知されることになります。
- 注意
プロセス間のタイミングを取るために通常の変数(boolやint)を使うのは誤りです。
プロセス間のタイミングでは、必ずsc_eventもしくはチャネル(sc_signal<>やsc_buffer<>)を使わないといけません。
そうしないと、シミュレーション誤動作の原因となります。
プロセス間のタイミングでは、必ずsc_eventもしくはチャネル(sc_signal<>やsc_buffer<>)を使わないといけません。
そうしないと、シミュレーション誤動作の原因となります。
2つのスレッドがお互いに同期をとる記述例
#include <systemc.h>
SC_MODULE( TOP )
{
sc_event e0; // イベント宣言
sc_event e1; // イベント宣言
SC_CTOR( TOP ) {
SC_THREAD( thread0 );
SC_THREAD( thread1 );
}
void thread0() {
cout << "--- thread0 start ---" << endl;
wait( SC_ZERO_TIME );
while (true)
{
e0.notify(); // イベント通知
wait( e1 ); // イベントを待つ
cout << sc_time_stamp() << " : thread0" << endl;
wait( 10, SC_NS );
}
}
void thread1() {
cout << "--- thread1 start ---" << endl;
while (true)
{
wait( e0 ); // イベントを待つ
cout << sc_time_stamp() << " : thread1" << endl;
wait( 10, SC_NS );
e1.notify(); // イベント通知
}
}
};
int sc_main( int argc, char* argv[] )
{
TOP top( "top" );
sc_start( 100, SC_NS );
return 0;
}
- 実行結果
--- thread0 start --- --- thread1 start --- 0 s : thread1 10 ns : thread0 20 ns : thread1 30 ns : thread0 40 ns : thread1 50 ns : thread0 60 ns : thread1 70 ns : thread0 80 ns : thread1 90 ns : thread0
実行結果をみると、thread0とthread1が交互に動作していることがわかります。
thread0は、イベント通知→イベント待ち→標準出力→wait(10ns)、
thread1は、イベント待ち→標準出力→wait(10ns)→イベント通知、
の順番となっています。
thread0は、イベント通知→イベント待ち→標準出力→wait(10ns)、
thread1は、イベント待ち→標準出力→wait(10ns)→イベント通知、
の順番となっています。
ここで、1点注目してほしいのは、thread0のwhile(true)文の前で、waitを1回呼んでいることです。
wait( SC_ZERO_TIME );
これは、必要です。
なぜならば、イベントの発生を知るためにはそのイベントを常に監視していなければならない、というルールがあるからです。
なぜならば、イベントの発生を知るためにはそのイベントを常に監視していなければならない、というルールがあるからです。
もし、このwait記述がないと正しく動作してくれません。でも動作することもあります。あいまいです。
それは、最初の動作時にthread0のe0.notify()とthread1のwait(e0)のどちらが先に実行されるかがあいまいだからです。
thread1のwait(e0)が先に実行されれば所望の動作しますが、thread0のe0.notify()が先に実行されてしまうと、イベント発行を見逃してしまうことになります。
それは、最初の動作時にthread0のe0.notify()とthread1のwait(e0)のどちらが先に実行されるかがあいまいだからです。
thread1のwait(e0)が先に実行されれば所望の動作しますが、thread0のe0.notify()が先に実行されてしまうと、イベント発行を見逃してしまうことになります。
誤った使い方
- 1つのスレッド内で使おうとした例
次の記述は明らかに誤りです。
SC_MODULE( TOP )
{
sc_event m_evnt;
SC_CTOR( TOP ) {
SC_THREAD( thread0 );
}
void thread0 {
while (true) {
m_evnt.notify();
/* ... */
wait( m_evnt );
}
}
};
m_evnt.notify()にてイベントを発行していますが、それを受けるwaitの位置がおかしいです。
本来であれば別プロセスにwaitを用意しなければいけません。
本来であれば別プロセスにwaitを用意しなければいけません。
まとめ
- イベントは、2つのプロセスの同期をとるためのもの。
- ルールとして、イベントは常に監視しなければ、イベント発行を見逃してしまう。