16
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C++Advent Calendar 2015

Day 21

C++ のコルーチン boost::coroutines を使ってみる

Last updated at Posted at 2015-12-21

C++ でコルーチンを使ってみます。

僕の心は生粋の(?)Windowsプログラマであり一昔前は CreateFiber でコルーチン的なものを自作していましたが、現在は Boost C++ Libraries にコルーチン機能が含まれているので、それを用いてコルーチンを利用します。

環境

  • Windows 7 Home Premium 64bit
  • Visual Studio 2013

Boost の入手と設置

Boost C++ Libraries のサイトから Boost のアーカイブを取得します。
今回は(たまたま手元にあったので)boost_1_55_0.zip で。

これを解凍して C:\boost_1_55_0 として設置。

Coroutine 関連のライブラリをビルド

Boost はヘッダのインクルードだけで利用できる機能も多いですが、一部機能はライブラリのビルドが必要になります。今回利用する boost::coroutines もビルドが必要なので、まずビルドを行っていきます。

bjam 生成

VS2013 のコマンドプロンプトを開き、 C:\boost_1_55_0 内で bootstrap.bat を実行。bjam.exe というビルド用のツールが生成されます。

PATH設定

C:\boost_1_55_0 に PATH を通しておきます。(bjam.exe を利用するため)

必要なモジュールのビルド

coroutine のビルドを行います。併せて、それに依存する context, system のビルドも行っておきます。

> cd C:\boost_1_55_0\libs\coroutine\build
> bjam --toolset=msvc-12.0 link=static,shared threading=multi release debug stage
> cd C:\boost_1_55_0\libs\context\build
> bjam --toolset=msvc-12.0 link=static,shared threading=multi release debug stage
> cd C:\boost_1_55_0\libs\system\build
> bjam --toolset=msvc-12.0 link=static,shared threading=multi release debug stage

以上。これで必要なライブラリがビルドできました。

プログラムからの利用準備

Visual Studio 2013 のプロジェクトの Properties にて。

C/C++ - General - Additional Include Directories に以下を追加設定。

C:\boost_1_55_0

Linker - General - Additional Library Directories に以下を追加設定。

C:\boost_1_55_0\libs\coroutine\build\stage;C:\boost_1_55_0\libs\context\build\stage;C:\boost_1_55_0\libs\system\build\stage

サンプル1:簡単な利用例

yield 値として int 値を用いた簡単なループ。

関数 loop() に突入した後、(そこのスタック情報を保持したまま)ブロックを抜け、sample1() のブロックに移動し、int 値の printf を行い、また loop() に突入し直し、前回の途中からの処理を再開していることがお分かりいただけるだろうか。

sample1.cpp
#include <stdio.h>
#include <boost/coroutine/coroutine.hpp>

void loop(boost::coroutines::coroutine<int()>::caller_type& yield)
{
	printf("loop start.\n");
	for (int i = 0; i < 5; ++i) {
		yield(i);
	}
	printf("loop end.\n");
}

void sample1()
{
	printf("sample1 start.\n");
	boost::coroutines::coroutine<int()> routine(loop);
	while (routine) {
		int n = routine.get();
		printf("n = %d\n", n);
		routine();
	}
	printf("sample1 end.\n");
}
出力結果
sample1 start.
loop start.
n = 0
n = 1
n = 2
n = 3
n = 4
loop end.
sample1 end.

サンプル2:応用例

個人的な好みとして、クラスをインスタンス化するだけでスレッドやコルーチンが自動稼働しだすモデルが好きなので、今回はそんなクラス Coroutine を作ってみた。Coroutine クラス実装は GitHub リポジトリを参照

※ここからやや自分世界に入っていて解説が特殊なので注意。分かりやすく書けなくてすみません。折を見て別記事にて解説し直したい。

ゲーム実装で使うことを想定している。(ゲーム実装の文化の背景についてはすみません適当にお察しお願いします。文字数が足りません。)
Coroutine を継承したクラスを定義し、run() をオーバーライドすることで、そのクラスがインスタンス化された後に自動的に run() が呼び出される仕組み。run() の中身は以下のように組まれる想定。

class MyCoroutine : public Coroutine{
    void run()
    {
        hoge(); // 1フレーム目の処理
        yield(); // wait
        hoge(); // 2フレーム目の処理
        yield(); // wait
        hoge(); // 3フレーム目の処理
        yield(); // wait
        // ここまで来るとインスタンスは自殺.
    }
};

コルーチンは CoroutineManager により駆動する。

  • CoroutineManager::instance()->go(); で1フレーム分駆動。
  • while (CoroutineManager::instance()->go()){ sleep(); } で全コルーチンがすべて終わるまで無限に駆動。

利用コードの完全なコード例は以下。

sample2.cpp
class MyCoroutine : public Coroutine{
public:
	MyCoroutine(std::string name, int cnt){
		m_name = name;
		m_cnt = cnt;
		printf("%s start.\n", m_name.c_str());
	}
	~MyCoroutine(){
		printf("%s end.\n", m_name.c_str());
	}
	void run(){
		for (int i = 0; i < m_cnt; i++){
			yield();
			printf("%s %d\n", m_name.c_str(), i);
		}
	}
private:
	std::string m_name;
	int m_cnt;
};

void sample2()
{
	new MyCoroutine("hello", 4);
	new MyCoroutine("world", 2);
	
	printf("manager start.\n");
	while (CoroutineManager::instance()->go()){
		printf("manager sleep\n");
	}
	printf("manager end.\n");
}
出力結果
hello start.
world start.
manager start.
manager sleep
hello 0
world 0
manager sleep
hello 1
world 1
manager sleep
hello 2
world end.
manager sleep
hello 3
manager sleep
hello end.
manager end.

この↑解説でしっくり来てくれた奇特な方にとって、以下に続く解説はとてもEasyです。

サンプル3:ラムダ式によりコルーチン処理を定義する

ちょっとした小さい処理をコルーチンで実行したい場合に、そのためだけにひとつクラスを定義するのはちょっと負荷が大きいので、もう少し手軽にコルーチンを駆動させる手段を提供したい。

TinyCoroutine というクラスを作った。このクラスは、コンストラクタにラムダ式を渡すだけでその処理がコルーチン上で実行される仕組みを提供する。

sample3.cpp
void sample3()
{
	new TinyCoroutine([](Coroutine* coroutine){
		for (int i = 0; i < 3; i++){
			printf("aa %d\n", i);
			coroutine->yield();
		}
	});
	new TinyCoroutine([](Coroutine* coroutine){
		for (int i = 0; i < 2; i++){
			printf("bb %d\n", i);
			coroutine->yield();
		}
	});

	printf("manager start.\n");
	while (CoroutineManager::instance()->go()){
		printf("manager sleep\n");
	}
	printf("manager end.\n");
}
出力結果
manager start.
aa 0
bb 0
manager sleep
aa 1
bb 1
manager sleep
aa 2
manager sleep
manager sleep
manager end.

以上、俺俺クラスの紹介になってしまった。反省は少ししている。

サンプルプロジェクト

以下に今回のサンプルプロジェクトを上げました。
https://github.com/kobake/BoostCoroutineSample

16
16
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
16
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?