LoginSignup
9
10

More than 5 years have passed since last update.

C++14 & boost::{coroutines|signals2} -> yieldable task system

Last updated at Posted at 2016-04-30

目的

yieldable ( ≈ generative ≈ enumerable ) な task ( ≈ functors ) システムを C++14 と boost::coroutines と boost::signals2 で簡単に実装できて便利に使えるので紹介したい。

補助説明

  • yieldable: C# の yield return のように関数の中間段階で関数の外に結果を渡せる仕組み。表現として yield から連想しやすい事からこの記事では yieldable とした。プログラミング言語一般論としてはジェネレーターと呼ばれる事もある機能。 boost::coroutines により使用可能。
  • task system: 様々な処理内容に対応可能な functor 群の実行を管理してくれる機能としてこの記事では task system と表現した。 functor は C++ においては lambda expression や operator() を実装したユーザー定義型による関数オブジェクト、Cスタイルの関数ポインターなど関数呼び出しとして振る舞う事のできるオブジェクトの意味。
  • C++14: この記事で紹介するソースコードは C++14 以降の言語規格に対応したコンパイラーを使わないとそのままでは翻訳できない。C++14の恩恵として lambda expression の引数での auto によるテンプレート化、それによるコードの記述の簡略化などの恩恵を受けている。
  • boost::signals2: C++ では Qt などでお馴染みの signal/slot 機能を実現するための boost の実装。今回は複数の task を slot として task system の signal に持たせる事で簡単な task system の実装として使っている。スレッドセーフな点も便利で安心して使える。

yieldable task system

  • yieldable_task_system.hxx:
#include <boost/coroutine/all.hpp>
#include <boost/signals2/signal.hpp>
#include <memory>

namespace usagi
{

class yieldable_task_system_type
{
  boost::signals2::signal< auto () -> void > signal;
public:
  template < typename COROUTINE_TYPE >
  auto push( COROUTINE_TYPE&& coroutine ) -> void
  {
    push( boost::coroutines::coroutine< void >::pull_type( std::move( coroutine ) ) );
  }

  auto push( boost::coroutines::coroutine< void >::pull_type&& coroutine ) -> void
  {
    auto shared_coroutine  = std::make_shared< boost::coroutines::coroutine< void >::pull_type >( std::move( coroutine ) );
    auto shared_connection = std::make_shared< boost::signals2::connection >();
    *shared_connection = signal.connect
    ( [ = ]
      {
        if ( not (*shared_coroutine)() )
          shared_connection->disconnect();
      }
    );
  }

  auto consume_one() -> bool
  {
    signal();
    return not signal.empty();
  }

  auto consume_all() -> void
  {
    while ( consume_one() );
  }

  auto operator()() -> void
  {
    consume_all();
  }
};

}

使用例

  • main.cxx:
#include "yieldable_task_system.hxx"
#include <chrono>
#include <iostream>

auto main() -> int
{
  using namespace std::chrono;

  usagi::yieldable_task_system_type yieldable_task_system;

  yieldable_task_system.push
  ( [ t0 = high_resolution_clock::now() ]
    ( auto& yield )
    {
      std::cout << 'a' << std::flush;
      yield();

      do
      {
        std::cout << 'b' << std::flush;
        yield();
      }
      while ( high_resolution_clock::now() - t0 < microseconds( 512 ) );

      std::cout << 'c' << std::flush;
      yield();
    }
  );

  yieldable_task_system.push
  ( []
    ( auto& yield )
    {
      for ( const auto value : { 'X', 'Y', 'Z' } )
      {
        std::cout << value << std::flush;
        yield();
      }
    }
  );

  yieldable_task_system.push
  ( []
    ( auto& yield )
    {
      std::cout << '1' << std::flush;
      yield();
      std::cout << '2' << std::flush;
      yield();
    }
  );

  yieldable_task_system();
}

結果

aX1bY2bZbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc

(註: 処理中に経過時間を観測しているため、実行ごとに結果の b の出力回数は異なる可能性があります。)

wandbox

Reference

9
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
10