5

我是 Scala 的新手,我想编写一些带有模式匹配的多线程代码,我想知道我是否可以将模式匹配代码视为原子的。

例如:

abstract class MyPoint
case class OneDim(x : Int) extends MyPoint
case class TwoDim(x : Int, y : Int) extends MyPoint

var global_point : MyPoint = new OneDim(7)

spawn {
    Thread.sleep(scala.util.Random.nextInt(100))
    global_point = new TwoDim(3, 9)
}
Thread.sleep(scala.util.Random.nextInt(100))

match global_point {
    case TwoDim(_, _) => println("Two Dim")
    case OneDim(_) => println("One Dim")
}

是否有可能执行如下:

  1. 主线程到达“匹配 global_point”代码,发现 *global_point* 不是TwoDim类型并暂停(返回到调度程序)。
  2. 生成的线程将 *global_point* 更改为TwoDim类型
  3. 主线程返回,发现 *global_point* 不是OneDim类型,认为没有匹配到 *global_point* 并引发 NoMatch 异常。

Scala是否在内部避免了这种执行?如果是这样,那怎么办?匹配是否拍摄对象的快照,然后尝试将其与模式匹配?快照深度是否有限制(匹配模式可能很复杂和嵌套)?

4

3 回答 3

4

这不是规范中的确凿证据,但它说明了编译器为您所做的一些事情,这应该允许您将一些匹配块视为原子 -但绝对不是全部。如果您自己同步代码,或者使用不可变对象,这会更安全。

平面示例

如果您使用以下脚本运行scala -print

var m: Option[String] = _

m match {
  case Some(s) => "Some: " + s
  case None => "None"
}

您将看到编译器创建的脱糖中间代码(为简洁起见,我删除了一些代码):

final class Main$$anon$1 extends java.lang.Object {
  private[this] var m: Option = _;

  private <accessor> def m(): Option = Main$$anon$1.this.m;

  def this(): anonymous class Main$$anon$1 = {
    <synthetic> val temp1: Option = Main$$anon$1.this.m();

    if (temp1.$isInstanceOf[Some]()) {
      "Some: ".+(temp1.$asInstanceOf[Some]().x())
    else if (scala.this.None.==(temp1))
      "None"
    else
      throw new MatchError(temp1)
  }
}

引用的可能共享对象m获得一个本地别名temp1,因此如果m在后台更改以使其指向另一个对象,则匹配仍然发生在所m指向的旧对象上。因此,您上面描述的情况(更改global_point为指向 aTwoDim而不是指向 a OneDim)不是问题。

嵌套示例

通常情况下,编译器似乎会为绑定在匹配案例保护中的所有对象创建本地别名,但它不会创建深层副本!对于以下脚本:

case class X(var f: Int, var x: X)

var x = new X(-1, new X(1, null))

x match {
  case X(f, ix) if f >  0 || ix.f > 0  => "gt0"
  case X(f, ix) if f <= 0 || ix.f <= 0 => "lte0"
}

编译器创建这个中间代码:

private[this] var x: anonymous class Main$$anon$1$X = _;

private <accessor> def x(): anonymous class Main$$anon$1$X = Main$$anon$1.this.x;

final <synthetic> private[this] def gd2$1(x$1: Int, x$2: anonymous class Main$$anon$1$X): Boolean = x$1.>(0).||(x$2.f().>(0));

final <synthetic> private[this] def gd3$1(x$1: Int, x$2: anonymous class Main$$anon$1$X): Boolean = x$1.<=(0).||(x$2.f().<=(0));

def this(): anonymous class Main$$anon$1 = {
  <synthetic> val temp6: anonymous class Main$$anon$1$X = Main$$anon$1.this.x();

  if (temp6.ne(null)) {
    <synthetic> val temp7: Int = temp6.f();
    <synthetic> val temp8: anonymous class Main$$anon$1$X = temp6.x();
    
    if (Main$$anon$1.this.gd2$1(temp7, temp8))
      "gt0"
    else if (Main$$anon$1.this.gd3$1(temp7, temp8))
      "lte0"
    else
      throw new MatchError(temp6)
  } else
    throw new MatchError(temp6)
}

x在这里,编译器为您匹配的对象及其两个子对象x.f(绑定到f)和x.x(绑定到)创建本地别名ix,但不为ix.f. 因此,如果您匹配的结构是深度嵌套的,并且您的案例依赖于您未在本地绑定的嵌套对象,则可能会发生竞争条件。众所周知,由于墨菲定律,并且会。

于 2012-08-14T07:18:13.163 回答
1

我认为为模式匹配生成的代码中没有任何类型的锁定或快照。如果是这样,它肯定会在语言规范中提到。话虽如此,如果您将模式匹配代码放入一个方法中,您至少可以确保该方法获得的引用在方法执行时不会改变。如果 TwoDim 和 OneDim 也是不可变的,那么您无需担心线程安全。即使它们不是不可变的,只要您在它们的可变字段之一上不匹配也没关系。

于 2012-08-14T07:01:27.023 回答
1

在 Scala 中,首选的并发解决方案是使用Actors。在那些Actors 中,您需要使用模式匹配来添加行为。Actor我使用与您类似的 scalas 构建了一个示例。

import scala.actors.Actor
import scala.actors.Actor._

abstract class MyPoint
case class OneDim(x: Int) extends MyPoint
case class TwoDim(x: Int, y: Int) extends MyPoint

case object Next
case object End

class OneDimSlave extends Actor {

  def act() {
    println("Starting OneDimSlave")
  loop {
        receive {
          case End => println("Stoping OneDimSlave"); exit()
          case Next => sender ! OneDim(scala.util.Random.nextInt(100))
        }
      }
}

}

class TwoDimSlave extends Actor {

  def act() {
    println("Starting TwoDimSlave")
  loop {
    receive {
      case End => println("Stopping TwoDimSlave"); exit()
      case Next => sender ! TwoDim(scala.util.Random.nextInt(100),scala.util.Random.nextInt(100))
    }
  }
}

}

class Master extends Actor {

  val oneDimSlave = new OneDimSlave
  val twoDimSlave = new TwoDimSlave

  oneDimSlave.start
  twoDimSlave.start

  def act {
    println("Starting Master")
for (_ <- 0 to 9) { oneDimSlave ! Next; twoDimSlave ! Next }
var count = 0
loop {
  if (count >= 20) { oneDimSlave ! End; twoDimSlave ! End; println("Stopping Master"); exit() }
  receive {
    case _ :OneDim => count += 1; println(count + ". One Dim")
    case _: TwoDim => count += 1; println(count + ". Two Dim")
  }
}
  }

}

object Main extends App {
  val master = new Master
  master.start
}

首先,我创建了消息。你只在 s 之间发送不可变Actor,所以我使用了 case 对象。您也可以包装答案,但在这种情况下,不需要它。接下来我创建了两种奴隶,MyPoint如果他们收到一个新的,他们只是返回一个。最后我创建了一个大师。主服务器初始化两个从服务器,每种一个,然后启动它们。之后,他向Next每个人发送了 10 次。然后他收到回复并打印出来。当收到所有响应时,主机发送End到每个从机并终止。我得到以下输出:

Starting TwoDimSlave
Starting OneDimSlave
Starting Master
1. One Dim
2. Two Dim
3. Two Dim
4. Two Dim
5. Two Dim
6. Two Dim
7. Two Dim
8. Two Dim
9. Two Dim
10. Two Dim
11. Two Dim
12. One Dim
13. One Dim
14. One Dim
15. One Dim
16. One Dim
17. One Dim
18. One Dim
19. One Dim
20. One Dim
Stopping Master
Stopping OneDimSlave
Stopping TwoDimSlave
于 2012-08-14T07:13:20.980 回答