Scalaで簡単に並列処理

Scalaで並列処理を行う場合、最も一般的なやり方はAkkaを使うやり方だと思います。

ですが、その処理がSeqとその順次実行によって表現出来る場合には、ParSeqを利用することで非常に簡単に並列処理プログラムを実装することができます。

例えば、Hello, Worldを10回表示するプログラム

for(i <- 1 to 10) { println("Hello, World") }

は、

(1 to 10) foreach { _ => println("Hello, World") }

のようにSeq(の一種であるIndexedSeq)を利用したプログラムに書き換えが可能で、

(1 to 10).par foreach { _ => println("Hello, World") }

のように書き換えることで非同期処理へと書き換えることができます。

簡単な解説

Seqトレイトは自身をParSeqへと変換するメソッドparを持っています。ParSeqは、その要素に対して順次実行する処理を並列化する仕組みをそなえたSeqであり、この性質を利用することで処理の並列化が図れる、ということになります。

ためしに、上記の処理にThread.sleep(500)を含めて実験してみると、並列処理の威力がわかります。

注意点と利点

ParSeqの順次実行処理はすべて並列化されてしまいます。そのため、実行順序が重要であるような処理を実現する場合、ParSeqは向きません。というか出力結果が破壊されてしまいます。これは並列処理一般に言える難しさです。

その一方で、ParSeqじたいはその要素の順序を保存するため、たとえば実処理はParSeqのmapによって実装し、その結果を実行後に収集する、という操作をすることで、順序を保存したままに並列処理を実現することが可能です。例えば、全てのユーザーの名前を大文字にして連結したい場合の実装を考えると、以下のようになります。

// ダメな例
val sb = new StringBuilder
List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par foreach {
 x => sb append x.toUpperCase
}

// 正解
List("Smith","Jones","Frankenstein","Bach","Jackson","Rodin").par.map(_.toUpperCase).reduce(_+_)
// -> "SMITHJONESFRANKENSTEINBACHJACKSONRODIN"という結果が得られるはずです

参考