単純な状態遷移ならば有限状態機械で十分なのですが、
処理の割り込みや処理同士の依存関係などがある場合では有限状態機械では限界を感じます
そこで処理自体をオブジェクトとして扱ったほうが何かと便利なことが多いです
ここでは処理オブジェクトをタスクとして扱っています
今回の全ファイル
zip:tasksystem.zip
つまり、タスクシステムとは、処理をオブジェクト単位(タスク)で扱い、
(タスク実行中でも)タスクの追加(+割り込み)・削除・取替に柔軟に対応できるシステムです
タスクシステム実装にあたり、メモリプールと呼ばれる連続メモリ領域をあらかじめ用意し、
そこから配置newでメモリ確保するようにしています
理由は通常のnewはメモリ領域の探索とメモリ確保が低速なため
頻繁に必要になる処理オブジェクトの生成処理でシステム全体が遅くならないようにするためです
また、処理に必要なメモリ量も自分で把握できるというメリットもあります
このように予め確保したメモリ領域を必要に応じて切り割りして割り当てる機能をアロケーターといいます
タスクには予め優先度の情報を持たせておき
タスクリストにタスクを優先度昇順になるように追加していき、優先度昇順に実行します
つまり、優先度の値が小さければ、先に処理されるため、
割り込みしたければ優先度の値を小さくしてタスクリストに追加します
優先度が同じであれば追加したタスク順に実行します
さらにタスク生成のため、タスクのサイズ分確保できるメモリ領域の探索が必要になるわけですが
探索の高速化のためにタスクのメモリ上でのリストの実装もしてあります
これはカーソルが一周した場合や、割り込みなどによるタスクの処理順序変更で、
連続メモリが断片化していることがあるので
メモリ上順番の関係にあるタスク間の断片領域を計算し、
新たに追加するタスクを確保できる領域ならば追加、
そうでなければ、次の断片領域に移るという処理を行います
この方法は1バイト単位で空き領域を探索するという手法よりはるかに高速です
連続メモリの容量は予め決められたサイズなので
大量のタスクが追加されてしまうと容量が足りなくなる場合が出てきます
(本来は、そうならないようなサイズを予め算出しておくのが好ましいが・・・)
その場合、メモリを拡張する必要があります
通常のポインタでタスク間の連結を実装してしまうと
メモリ拡張に伴い、そのまま全タスクをコピーしたとき
ポインタの関係が崩れ、全タスクに対し、タスクリストのつなげ直しが必要になってしまいます
それを防ぐためにメモリプールの先頭アドレスからの相対位置(相対アドレス)を
タスク同士の連結の情報として持っておきます
実際のアドレスは連続メモリ領域(メモリプール)の先頭アドレスに相対アドレスを加算することで求められます
タスクの削除についてですが
これにはタスクの生成時にタスクIDを振り
そのIDが振られているタスクに関して、削除を行います
この際、 実行中のタスクのメモリ領域の変更・上書き・削除などは一切できないので
実行中のタスクは削除しないようにしなければなりません
タスクの差し替えに関しては、指定IDのタスクを削除して新しいタスクを追加するので説明は不要でしょう
追加済みのタスクの優先度を変更させることもできます
これは指定IDのタスクの優先度を一括で変更し、タスクリストを優先度昇順でマージソートします
ソート処理自体は負荷が大きい処理なので出来る限り使わないほうが好ましいのですが
(できる限りタスクの追加のときに優先度を調整する)
割り込みの処理同様、必要になることがあるので追加しておきました
今回の実装は
・task.cpp
・task.h
に記述してあります
使い方は以下のようにします
各タスクはTaskクラスを継承して実装します
タスク間の共通データはTaskSystemクラスを継承したTaskSystemExクラスを用意します
実際のタスク生成はTaskSystemクラスのAddTaskメンバ関数で行います
この際、デフォルトの優先度より低く設定すると既にタスクリストに追加されているタスクよりも
先に実行することができます(割り込み処理)
また、すでに登録したタスクをDeleteTaskメンバ関数を用いることでID指定で削除することが可能です
タスクの差し替えは、タスクをID指定で差し替えることができます
main.cpp
#include "task.h" #include <crtdbg.h> #include <windows.h> #include <conio.h> class InputTask; class NetworkTask; class DrawTask; class Draw3DTask; class Draw2DTask; class DrawObjectTask; // タスクID enum{ TASKID_INPUT, TASKID_NETWORK, TASKID_DRAW, TASKID_DRAW3D, TASKID_DRAW2D }; class TaskSystemEx : public TaskSystem { public: int common; // タスク間共通データ TaskSystemEx():common(0){} virtual ~TaskSystemEx(){} }; // キャストめんどくさいので置き換え #define m_pTaskSystemEx dynamic_cast<TaskSystemEx*>(m_pTaskSystem) class InputTask : public Task { public: virtual void Execute(){ m_pTaskSystemEx->common++; printf("%d周目 InputTask id = %d\n",m_pTaskSystemEx->common,GetTaskID()); m_pTaskSystem->AddTask<NetworkTask>(TASKID_NETWORK); } }; class NetworkTask : public Task { public: virtual void Execute(){ printf("%d周目 NetworkTask id = %d\n",m_pTaskSystemEx->common,GetTaskID()); m_pTaskSystem->AddTask<DrawTask>(TASKID_DRAW); } }; class DrawTask : public Task { public: virtual void Execute(){ printf("%d周目 DrawTask id = %d\n",m_pTaskSystemEx->common,GetTaskID()); m_pTaskSystem->AddTask<InputTask>(TASKID_INPUT); // プライオリティでタスクの割り込み(低い順に実行される) for(int i = 0;i < 10;++i) m_pTaskSystem->AddTask<Draw2DTask>(TASKID_DRAW2D,(double)rand()/RAND_MAX * 0.5); for(int i = 0;i < 10;++i) m_pTaskSystem->AddTask<Draw3DTask>(TASKID_DRAW3D,0.2); // (現在実行中以外の)指定のIDのタスクを全て差し替え m_pTaskSystem->SwapTask<DrawObjectTask>(TASKID_DRAW3D); // (現在実行中以外の)全タスクを全て削除 //m_pTaskSystem->DeleteAllTask(); // (現在実行中以外の)指定のIDのタスクの優先度を全て差し替え m_pTaskSystem->SetTaskPriority(TASKID_DRAW3D,0.3); // (現在実行中以外の)指定のIDのタスクを全て削除 //m_pTaskSystem->DeleteTask(TASKID_DRAW2D); } }; class Draw3DTask : public Task { public: char a[100]; // 適当なデータ virtual void Execute(){ printf("%d周目 Draw3DTask id = %d\n",m_pTaskSystemEx->common,GetTaskID()); } }; class Draw2DTask : public Task { public: char a[120];// 適当なデータ virtual void Execute(){ printf("%d周目 Draw2DTask id = %d priority = %lf\n",m_pTaskSystemEx->common, GetTaskID(),GetPriority()); } }; class DrawObjectTask : public Task { public: virtual void Execute(){ printf("%d周目 DrawObject id = %d priority = %lf\n",m_pTaskSystemEx->common, GetTaskID(),GetPriority()); } }; int main() { #ifdef _DEBUG _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF); //_CrtSetBreakAlloc(x); #endif TaskSystemEx tasksystem; tasksystem.Create(1000); tasksystem.AddTask<InputTask>(TASKID_INPUT); while(tasksystem.ExecuteTask()){ // 出力が見やすいように0.1秒休む //Sleep(100); // キー入力で終了 if(_kbhit()) break; } // メモリダンプ tasksystem.Dump("dump.txt"); return 0; }
追記:デフラグの処理は追加してあるのですが、
断片領域が増えた時の探索にかかるコストとデフラグにかかるコストのつり合いがわからないため、未使用です