C++で周期処理テンプレートクラスの実装

2020/05/19

C/C++ 技術

t f B! P L

皆様こんばんは

今日はC++で周期処理を行う例です。

あらかじめ決められた周期で、ネットワークに接続された機器のポーリングなどに使われることが多い処理ですが、sleepやsleep_forのようなプログラムをブロックしてしまう手段を用いて実現してしまうと、処理停止中に何らかの対応を求められた場合に即座に対応できません。

そこで今回はプログラムブロックすることなく柔軟に処理を引き受けて周期処理を実現するテンプレートクラスを書いてみます。

ソースコード


解説

単独で使用した場合使いにくいクラスでしたが、今回の様に周期処理の仕組みを組み込んでやると、関数オブジェクトで特殊化してインスタンスに関数オブジェクトを与えてやるだけで、既存の処理を周期処理に置き換えることが出来ます。
従来処理の呼び方をあまり変えずにプログラムの流れを加工したい場合には便利で、今回はその例でもあります。


インターバルと関数オブジェクトの設定 

クラスとして管理するのは関数オブジェクト、最近実行時間、インターバルの3点です。

 高速なサイクルが必要な場合はmicrosecondsで取っても良いですが。  
科学演算や精密な計測機に使われるようなプログラムでなければ、基本的にはミリ秒単位で十分でしょう。
 
    private:
    T_Function                              managed_function_;
    std::chrono::system_clock::time_point   time_point_holder_;
    std::chrono::milliseconds               interval_;
インターバルと、実行対象の関数オブジェクト、そして現在の時間を初期値として取ります。 
これで準備は完了です。
 
    Cyclic_Process(uint64_t interval_value, T_Function&& managed_function):
    managed_function_( managed_function ),
    time_point_holder_( std::chrono::system_clock::now() ),
    interval_( interval_value )
    {}


周期処理本体

    void call_function()
    {
        std::chrono::system_clock::time_point current_time = 
        std::chrono::system_clock::now();

        std::chrono::milliseconds measurement = 
std::chrono::duration_cast( current_time - time_point_holder_ ); if( interval_ <= measurement )
{ managed_function_(); time_point_holder_ = std::chrono::system_clock::now(); } }
周期処理の実行は、このオブジェクトを呼ぶ側のループ内などで呼ばれたときに、 前回の実行から実行周期が経過したことを確認して、経過していた場合に実行するという手法を取ります。

所謂ビジーウェイトになるかどうかは、外側の処理の実装の仕方に依存しますが、プログラムやスレッドをブロックせず、 外部からの指示に即座に応答することに適します。
 
        std::chrono::system_clock::time_point current_time = 
        std::chrono::system_clock::now();

        std::chrono::milliseconds measurement = 
        std::chrono::duration_cast(
            current_time - time_point_holder_
        );

        if( interval_ <= majorment )
        {
現在の時刻と、メンバ変数に記録しておいた前回の実行時間の差を取り、 その差がインターバルを超えていた場合に関数オブジェクトを呼び出します。

time_point が保持している値は分解能が高く、milliseconds 型で定義した measurement に代入するときには、 duration_cast を行ってミリ秒まで分解能を落とす必要があります。 h3. 周期処理実行方法
 
int main( int argc, char** argv )
{
        Cyclic_Process> cyclic_process(
            1000,
            [](){
                std::cout << "process executed." << std::endl;
            }
        );

        while(true)
        {
            /*スリープを使っていないので*/
            cyclic_process();
            /*サイクル処理の前後に自由な処理を挿入できる*/
        }
} 
これはお試しなので無限ループです。

ビジーループじゃねえのこれ?

違います。一応……

サンプルコード上はビジーループ処理に見えるかもしれません。 

しかしこのような周期処理の実現方法は、実際には大規模な処理を行っているプログラムの中で、 例えばスレッドの過剰な作成を抑制しながら、それほど精密な周期を必要としない周期処理を実行する場合などに使用する処理です。 

ゲームのフレームレートを決める場合などが分かりやすいでしょうか。 

1. 画面の更新を行うにあたり、グラフィックメモリにCGがレンダリングされている必要がある。
2. 描画が早すぎれば描画の実行を待たずに入力ボタンを取りに行く(処理をブロックせずにすぐ取りたい)。 
3. 描画が完了した状態が周期処理として設定された時間以上経過していれば、描画を実行。 このような処理の実装に向いています。 

またプログラム全体に足の遅い処理が複数あり、複数ヶ所で時間経過を確認したい場合などにも重宝します。 

さらには冒頭でも書きましたが、処理の復帰待ちが発生しないため、緊急的にプログラムをシャットダウンしたい場合に、 sleep や this_thread::sleep_for によるブロックの終了を待たず、即座に落とせる利点があります。 

これは例えば、暴走が人間の身に危険を及ぼす可能性のある、私の開発分野では大きな利点です。 

 さいごに  

sleep や this_thread::sleep_for 等の処理停止とスレッドを組み合わせた周期処理は、 一般的ですし、実際にCPUのリソースを節約しながら周期処理を行いたい場合などに有効です。 

しかしシビアなコントロールや、そもそも大規模なソフトウェアでのスレッドを使用したくない差し込み周期処理が必要な場合には、今回ご紹介した内容の方が向いていると言えます。  

実際に私の設計開発しているシステム自体、スリープ型の周期処理を提供するクラスと、 今回のような非ブロック型の周期処理を提供するクラスの2種類を適宜使い分けて様々な処理を実現しています。 

皆様の様々な周期処理を使い分ける候補として、今回ご紹介した内容が参考と慣れば幸いです。

Translate

ページビューの合計

注意書き

基本的にごった煮ブログですので、カテゴリから記事を参照していただけると読みやすいかと存じます。

ADBlocker等を使用していると、Twitterやアクセスカウンタが表示されません。

記事を読むには差し支えませんが、情報を参照したい場合には一時例外にしていただけると全てご参照いただけます。

Featured Post

ボイドラDICEの攻略法

QooQ