std.concurrency.Scheduler


次のdmd 2.067から利用出来るstd.concurrencyの`Scheduler`について書きます(これ書く頃には2.067が出るものと思ってました)．

std.concurrencyに関しては，去年のAdvent Calendarに二つ記事が上がっているので，参考にしてみてください．

- [Dive into std.concurrency](http://qiita.com/repeatedly/items/dbb858a4f4e3b2dffb51)
- [Dive in the deep std.concurrency](http://qiita.com/mono_shoo/items/2215ee83d6f3251d01dc)

## 2.066までのstd.concurrency

2.066まで，std.concurrencyのAPIは`spawn`くらいで，また裏ではスレッドが起動するというシンプルなものでした．しかし．std.concurrencyを使ったライブラリも少しずつ増え，スケジューリング方法を制御したい，という要望も出てきました．
2.067からは，`Scheduler`というクラスを使うことで，std.concurrencyのAPIを使いつつ，自分の好きなスケジューリングが出来るようになります．下記は`Scheduler`が実装されたPull Requestです．

[Issue 12090 - Make std.concurrency compatible with fibers as threads](https://github.com/D-Programming-Language/phobos/pull/1910)

## Scheduler API

`Scheduler`のAPIは今のところ，`Thread`のようなimplicitなスケジューリングと，`Fiber`のようなexplicitなスケジューリングの二つをカバーする以下のAPIになってます．APIは要望やカバーするケースによって今後増えたりするかもしれません．

```d
interface Scheduler
{
    void start(void delegate() op);
    void spawn(void delegate() op);
    void yield() nothrow;
    @property ref ThreadInfo thisInfo() nothrow;
    Condition newCondition(Mutex m)
}
```

現状，このAPIの参照実装として，`Thread`ベースの`ThreadScheduler`，`Fiber`ベースの`FiberScheduler`の二つが提供されています．

APIについてそれぞれ説明します.

- start

`op`を実行してスケジューリングを開始します．これは`FiberScheduler`のようなexplicitなスケジューリングが必要なスケジューラ向けです．`Scheduler`を使う時には，メインプログラムを`op`に中の入れ，`start`を呼び出すようにします．

- spawn

通常の`spawn`と同じセマンティクスです．`ThreadScheduler`なら`Thread`で，`FiberScheduler`なら`Fiber`で`op`を起動します．

- yield

`Fiber`の`yield`と同じセマンティクスです．explicitな`FiberScheduler`では`yield`を呼び，implicitな`ThreadScheduler`では，空になっています．

- thisInfo

該当する`ThreadInfo`オブジェクトを返します．`ThreadInfo`オブジェクトは，スケジューリングに必要なスレッド関係の情報を保持しているオブジェクトになります．これを経由して，スケジューラに関わっているデータをクリーンアップします．

- newCondition

`Scheduler`に特化した条件変数を返すために使います．通常であれば，条件変数の`wait`が呼ばれたらブロッキングしたりタイムアウトしたりするのが普通ですが，その辺りの挙動を変更したい場合などは，特殊化された条件変数を返すようにします．

## Schedulerの使い方

std.concurrencyに`scheduler`というグローバル変数があるので，これにSchedulerをセットします．そうするとstd.concurrencyの各操作は，この`scheduler`を参照して動くようになります．
以下の例は，`FiberScheduler`を使うように変更する例です．

```d
import std.stdio;
import std.concurrency;

void main()
{   
    scheduler = new FiberScheduler;
    scheduler.start({   
                        writeln("Hello!");
                    });
}
```

## Schedulerの使い所

今のところ，ほとんどの人は使う必要はないです．通常の利用ではデフォルトのスレッドベースで十分でしょう．少し凝ったことがしたい，例えばスレッドプールを使ったり，タスクに独自の優先順位をつけたい，といった状況であれば独自のSchedulerを実装することになります．

D言語で広く使われているイベント駆動なWebフレームワークvibe.dは，以下のような実装をすでにmasterで持ってます([github上でのコード](https://github.com/rejectedsoftware/vibe.d/blob/ad5f91269a0dfe5cc84fbbb10a4ec04b17ebc7d1/source/vibe/core/concurrency.d#L1178))．

```d
package class VibedScheduler : Scheduler
{
    override void start(void delegate() op) { op(); }
    override void spawn(void delegate() op) { runTask(op); }
    override void yield() {}
    override @property ref ThreadInfo thisInfo() { return Task.getThis().tidInfo; }
    override TaskCondition newCondition(Mutex m) { return new TaskCondition(m); }
}
```

vibe.dの場合にはイベント駆動IOとの兼ね合いもあるので，既存のスケジューラは使わずに，独自のタスクや条件変数を使うようにしています．今までは外側からこれと近いことをしてましたが，スケジューラと結合することで，かなり実装がシンプルになっています．

独自スケジューラを実装したい場合には，標準組み込みの二つのスケジューラや，外部ライブラリの実装を参照して見てると良いと思います．

## まとめ

まだ正式にリリースされていない機能ですが，次のバージョンから使えるということで，std.concurrencyの`Scheduler`を紹介しました．std.concurrencyは改善が活発なモジュールの一つでもあるので，また何か変更があれば随時記事にして行けたらなと思います．
また，一緒に入った`Generator`というクラスについても，時間を見つけて紹介する予定です．