并发编程中的Scala模式匹配

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. 主线程到达“match global_point”代码,发现*global_point*不是类型为TwoDim的对象,暂停(返回调度程序)。 2. 生成的线程更改*global_point*的类型为TwoDim。 3. 主线程返回,发现*global_point*不是类型为OneDim的对象,认为没有匹配到*global_point*,并引发NoMatch异常。
Scala内部是否避免这种类型的执行?如果是的话,怎么做?匹配是否会对对象进行快照,然后尝试将其与模式进行匹配?快照深度是否有限制(匹配模式可能很复杂和嵌套)?
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_pointOneDim指向改为TwoDim)不是问题。

嵌套示例

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

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创建别名。因此,如果您匹配的结构嵌套深,并且您的情况取决于您没有在本地绑定的嵌套对象,则可能会发生竞争条件。正如我们所知,由于墨菲定律,这种情况将会发生。

1
在Scala中,处理并发的首选解决方案是使用Actor。在这些Actor中,您需要使用模式匹配来添加行为。我使用Scala的Actor构建了一个类似于您的示例的示例。
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
}

首先我创建了消息。在Actor之间只能发送不可变的对象,所以我使用了case对象。你也可以将答案包装起来,但在这种情况下没有必要。接下来我创建了两种类型的从属者,如果它们收到一个“.”,它们就会返回一个新的MyPoint。最后我创建了一个主人。主人初始化了两个从属者,每种一个,并启动它们。然后他向每个从属者发送10次Next。然后他接收响应并打印它们。当所有响应都被接收时,主人向每个从属者发送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

1

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接