S-JIS[2011-02-26] 変更履歴

Scala スレッド

Scalaのスレッドのメモ。

ScalaでもJavaのThread・Runnable・synchronizedが(ほぼ)そのまま使えるが、
ScalaではActorを使う方が多いようだ。


Thread・Runnable

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`()

synchronized

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とは異なるが、自クラスをロックに使おうと思えば使える。

opsオブジェクトで定義されているメソッド

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 同期をとって値の更新が出来る

Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま