RustでScoped Threadを用いた並行処理


この記事は [Rustその2 Advent Calendar 2016](http://qiita.com/advent-calendar/2016/rust-lang-2) 20日目の記事としてかかれました。


# 環境と対象読者

この記事に含まれるコードは
`rustc 1.13.0 (2c6933acc 2016-11-07)`の
[Rust Playground](https://play.rust-lang.org)または[ここ](http://play.integer32.com/)で動くことを確認しました。

最適な対象読者としてはrustで並行処理するアプリケーションを
これから書きたいと思っている人です。


# std::threadの罠

rustの標準ライブラリの`std::thread`を用いて並行処理を書きたい時に
例えばこういうような処理が例示されるかと思います。

```rust
use std::thread;

fn add_one(x: i32) -> i32 {
    x + 1
}

fn main() {

    let y = 1i32;
    let handle = thread::spawn(move ||{println!("{}",add_one(y))});
    handle.join();    
}

```

この処理は問題ないと思います。（少し誤魔化している部分はありますが）
関数をspawnしてやれば1足してプリントしてくれます。

しかし、現実の問題はより複雑で、上記のように常に1足して終わるような問題は少なく、
そのうちこんな風なより一般化された処理を書きたくなります。


```rust
use std::thread;

struct Adder {
    x: i32
}

impl Adder {
    pub fn new(x:i32) -> Adder {
        Adder {x:x}
    }
    
    pub fn add_println(self,y:i32) {
        let h = thread::spawn(move ||{println!("{}",y+self.x)});
        h.join();
    }
    
}


fn main() {
    let y = 1i32;
    
    let adder = Adder::new(2);
    adder.add_println(y);
    
}

```

これで一般化されました。
threadを立ち上げて最初に与えた`i32`型の整数を加算してくれます。
しかし、上記には少し問題があり、それは一行足すだけで顕在化します。

```rust
use std::thread;

struct Adder {
    x: i32
}

impl Adder {
    pub fn new(x:i32) -> Adder {
        Adder {x:x}
    }
    
    pub fn add_println(self,y:i32) {
        let h = thread::spawn(move ||{println!("{}",y+self.x)});
        h.join();
    }
    
}

fn main() {

    let y = 1i32;

    let adder = Adder::new(2);
    adder.add_println(y);
    adder.add_println(y);
}

```

こうする事でこのプログラムはこんな風に怒られます。

```
error[E0382]: use of moved value: `adder`
  --> <anon>:31:5
   |
30 |     adder.add_println(y);
   |     ----- value moved here
31 |     adder.add_println(y);
   |     ^^^^^ value used here after move
   |
   = note: move occurs because `adder` has type `Adder`, which does not implement the `Copy` trait

error: aborting due to previous error

```

なるほど（なるほど）。
つまりstruct自体をmoveセマンティクスで移動しちゃってるので
コンパイル時にlifetimeチェッカーで怒られてしまってるわけですね。

ここで浅知恵を働かせ、次のように書き換えてみます。

```rust
use std::thread;

struct Adder {
    x: i32
}

impl Adder {
    pub fn new(x:i32) -> Adder {
        Adder {x:x}
    }
    
    pub fn add_println(&self,y:i32) {
        let h = thread::spawn(move ||{println!("{}",y+self.x)});
        h.join();
    }
    
}

fn add_one(x: i32) -> i32 {
    x + 1
}

fn main() {
    let y = 1i32;

    let adder = Adder::new(2);
    adder.add_println(y);
}
```
変更点は`add_println`メソッドの第一引数に渡していた`self`を`&self`としただけです。
つまり、`add_println`メソッドにはstructの参照を持たせ、
それ自体はmoveされても問題ないようにしようと考えました。

勘のいい方は既にお気付きの通り、この目論見は崩れ去ります。

```
error[E0477]: the type `[closure@<anon>:13:31: 13:63 y:i32, self:&Adder]` does not fulfill the required lifetime
  --> <anon>:13:17
   |
13 |         let h = thread::spawn(move ||{println!("{}",y+self.x)});
   |                 ^^^^^^^^^^^^^
   |
   = note: type must outlive the static lifetime

error: aborting due to previous error
```

なぜか。それは **spawnされたthreadは`'static`なlifetimeを持つ** からです。
一方で`Adder`のインスタンスはここでは`main`関数内に留まっています。
threadのlifetimeである`'static`より狭い範囲のためやはり
lifetimeチェッカーによって弾かれてしまいます。

この要件があるため`thread::spawn`する際はたいていの場合moveセマンティクスが必要になります。
上で少し誤魔化していると書いたのはこの部分です。

ここに来てプログラマはrustのスレッドはネイティブなスレッドであり、
rustはランタイムのない言語なんだったと言う事を改めて気付かされます。
（同時にC++でスレッドプログラミングをしたことのある方は
　この制限はある種の懐かしさを感じるのではないでしょうか。）

# crossbeam

前置きが長くなりましたがここで漸く本題です。
上のコードがエラーになるのは理屈としてはわかるんですが、
とは言えプログラマの心情的には納得できない部分もあります。

なぜなら`add_println`メソッドの中でthreadはjoin()しているわけなので、
threadのlifetimeはそこまでとわかっているからです。

このようなケースで有効になるのがscoped threadです。
これはまあ名前の通りscopeを限定したthreadです

rustにおけるscoped threadは以前はstd内に入っていたようですが、
今は[crossbeamという外部crate](https://crates.io/crates/crossbeam)を使うのが良いようです
自分のプロジェクトで使う場合はCargo.tomlに加えましょう。

ちなみに[こちらのRust playgroundには追加済みのようです](http://play.integer32.com/)

```
extern crate crossbeam;

struct Adder {
    x: i32,
}

impl Adder {
    pub fn new(x: i32) -> Adder {
        Adder { x: x }
    }

    pub fn add_println(&self, y: i32) {
        crossbeam::scope(|scope| {
            let h = scope.spawn(|| println!("{}", y + self.x));
            h.join();
        });
    }
}


fn main() {
    let y = 1i32;
    let adder = Adder::new(2);
    adder.add_println(y);
    adder.add_println(y);
}
```

これでようやく意図した通りの並行処理が実現できました。文明ですね。


# まとめ

rustのthreadはnativeなthreadなので
いろいろつらい部分もあるかもしれませんが、
crossbeamとか使ったら少し楽になるという話でした。



# 余談

[現在rust用のTUIライブラリを書いています。](https://github.com/yasuyuky/cursormatrix)
（まだまだbuggyなのでこれに依存したアプリケーションは書かないでください…）
今回のcrossbeamもこれの開発中で必要になりました。

もちろん、既にTUIの為のライブラリは存在していますし、
なぜ今更？と思われるかもしれませんが、
一番大きいのは既存のライブラリが複雑なUnicodeの仕様に
勝ててない感じというのがあります。
ここはeast asian widthな母語を持つ一人として
立ち上がろうと決心しました（勝てるとは言ってない）。
もちろん今回自分で書く上で既存のライブラリ群はとても参考にさせていただいています。

書いている上で様々なTipsがあり、学びが発生しましたので、
また機会があればそちらも紹介できればと思います