S-JIS[2011-02-26] 変更履歴
Scalaのスレッドのメモ。
ScalaでもJavaのThread・Runnable・synchronizedが(ほぼ)そのまま使えるが、
ScalaではActorを使う方が多いようだ。
ScalaはJavaのクラスがそのまま使えるので、java.lang.Threadクラスも使える。
java.langパッケージのクラスはScalaでもデフォルトでインポートされるので、Threadクラスは特にインポートしなくても使える。
class SampleThread(list: List[Int]) extends Thread { override def run() = { println(list.sum) } } val s = new SampleThread(List.range(1, 10000+1)) s.start //スレッドの実行開始 s.join //スレッドの終了待ち
→spawn … Scalaでのスレッド実行
→future … 別スレッドで実行して結果を受け取る
同じく、Runnableインターフェースも使える。(Scalaではトレイトとして扱う)
class SampleRunnable(list: List[Int]) extends Runnable { def run() = { println(list.sum) } } val s = new Thread(new SampleRunnable(List.range(1, 10000+1))) s.start s.join
スレッドを一時停止するsleep()メソッドは、Scalaでは例外のキャッチを明示的に書く必要が無いので、使いやすい。
(sleep()はthrows InterruptedExceptionが宣言されているので、Javaでは明示的にcatchを書く必要がある)
Thread.sleep(数値) //ミリ秒単位 Thread.sleep(m, n) //ミリ秒+ナノ秒単位 import java.util.concurrent.TimeUnit TimeUnit.SECONDS.sleep(数値) //秒単位
→alarm … 一定時間経過するまで(別処理を行いつつ)待つ
自分のスレッドを一時休止して別スレッドを動かすにはyield()メソッドを使用するが、
Scalaではyieldは予約語(for式で使われる)なので、バッククォートで囲む必要がある。
scala> Thread.yield()
<console>:1: error: identifier expected but 'yield' found.
Thread.yield()
^
scala> Thread.`yield`()
Javaで排他制御する為のsynchronized文(メソッドに付けるsynchronizedキーワード)は、ScalaではAnyRefクラスのメソッドという形になっている。
Scala | Java相当 | 備考 |
---|---|---|
private val lock = new AnyRef def f() = { lock.synchronized { 〜 } } |
private Object lock = new Object(); public void f() { synchronized(lock) { 〜 } } |
synchronizedメソッドは引数をそのまま返すだけのメソッドという形になっている。 Scalaでは引数が1つだけの場合は丸括弧の代わりに波括弧が使えるので、 メソッド呼び出しというより文のような形で書ける。 |
def f() = { this.synchronized { 〜 } } def f() = { synchronized { 〜 } } |
public void f() { synchronized(this) { 〜 } } |
ロックオブジェクトを作らずとも、自分自身のインスタンスもAnyRefを継承しているので、thisでsynchronizedメソッドを扱える。 自分自身(this)に対するメソッド呼び出しでは「 this. 」を省略できるので、synchronizedをいきなり書くことが出来る。 |
def f() = synchronized { 〜 } |
public synchronized void f() { 〜 } |
Scalaではメソッド本体が一つの式しか無い場合は本体を囲む波括弧を省略できる。 |
def f() = classOf[自クラス].synchronized { 〜 } |
public static synchronized void f() { 〜 } public static void f() { synchronized(自クラス.class) { 〜 } } |
Javaのstaticメソッドに対するsynchronizedは、自分のクラスオブジェクトをロックオブジェクトとする排他に等しい。 ScalaのobjectはシングルトンインスタンスなのでJavaのstaticとは異なるが、自クラスをロックに使おうと思えば使える。 |
Scalaでスレッドを便利に扱えるメソッドがscala.concurrent.opsオブジェクトに定義されている 。
import scala.concurrent.ops._
メソッド | Scalaの例 | Java相当 | 備考 |
---|---|---|---|
spawn |
def f() = { spawn { println(123) } println(456) } |
public void f() { new Thread() { @Override public void run() { System.out.println(123); } }.start(); System.out.println(456); } |
spawnは、別スレッドでただ実行されればいい(値が返ってくる必要が無い)という場合に使用する。 |
par |
val t = par( List.range(1, 5000+1).sum, List.range(5001, 10000+1).sum ) |
parは、2つの引数の処理のうち片方を別スレッドで実行し、両方の結果をペア(タプル)にして返す。 別スレッドで実行した処理から例外が発生した場合でも、parから例外が投げられる。 |
|
replicate |
val size = 100 val n = 4 val list = List.range(1, size*n+1) replicate(0, n) { i => val s = size * i val e = s + size println(list.slice(s, e).sum) } |
replicateは、同じ処理を複数スレッド作って実行したい場合に使用する。 replicate自体は、最後1つ分だけは自スレッドで実行する(それが終わるまで戻ってこない)。 そのスレッド以外はspawnで実行される。 処理本体は、Intを受け取るような関数とする。 replicateの最初の引数リストで(rangeと同様な)数値の範囲を指定する。 処理本体はその範囲内の番号を受け取り、その番号が示す部分の処理を行うようにする。 |
|
future |
//別スレッドで計算開始 val f = future { List.range(1, 10000+1).sum } //ここで別の処理を行う val r = f() //計算結果を取得する |
futureは、別スレッドで計算を実行し、計算結果を受け取る為に使用する。 futureの返すオブジェクトのapply()メソッドを呼ぶと結果が取れる。 計算が終了するまで、apply()メソッドは待ちに入る。 actorsパッケージのFuturesオブジェクトにもfutureメソッドがあるが、そちらの方が高機能かも。 |
クラス名 | 備考 | |
---|---|---|
java.util.concurrent.locks | Lock | ロック用のインターフェース |
java.util.concurrent.locks | Condition | 条件変数 |
java.util.concurrent.locks | ReentrantLock | 再入可能なロック |
java.util.concurrent.locks | Semaphore | セマフォ |
java.util.concurrent.locks | ReadWriteLock | 読み書きロック |
scala.concurrent | Lock | シンプルなロック |
scala.concurrent | SyncVar | 同期をとって値の更新が出来る |