[8] – Actor 与出新

Actor 是 Scala 基于音讯传递的起模型,尽管自 Scala-2.10
其默认并发模型的身价就让 Akka 取代,但这种与俗
Java、C++完全无雷同的起模型依然值得学习。

咋样使 Actor

扩展 Actor

先期来瞧第一种植用法,下边是一个简便例子及有表达

//< 扩展超类 Actor
class ActorItem extends Actor {
  //< 重载 act 方法
  def act(): Unit = {
    //< receive 从消息队列 mailbox 中去一条消息并处理
    receive { case msg => println(msg) }
  }
}

object Test {
  def main (args: Array[String]): Unit = {
    val actorItem = new ActorItem
    //< 启动
    actorItem.start()

    //< 向 item 发送消息
    actorItem ! "actor test1"
    actorItem ! "actor test2"
  }
}

输出:
actor test1

这种用法在实际中并无常用,需要:

  1. 恢宏超类 Actor
  2. 重载 act 方法
  3. Java,调用扩充类对象 start 方法

使用 scala.actors.Actor.actor 方法

第三种植方法是实在中常用并且是 Scala 社区推荐的,例子如下:

object Test {
  def main (args: Array[String]): Unit = {
    val actorItem = actor {
      receive { case msg => println(msg) }
    }

    //< 向 item 发送消息
    actorItem ! "actor test1"
    actorItem ! "actor test2"
  }
}

输出:
actor test1

此处要特别注意的是,actor 其实是scala.actors.Actor的 actor
方法,并无是 scala 语言内建的。
这种用格局更有利于,与第一种植扩充超类 Actor 有以下几点不同:

  1. 行使 Actor.actor 方法(再次回到路为Actor)而无是扩展 Actor 同等对待载 act
    方法
  2. 结构就就起步,不需要调用 start方法(当然你调用了邪无会晤发什么问题)

使用 react

而外可以下 receive 从信队列 mailbox 中取出音讯并拍卖,react
同样可以。receive 和 react
的区别及关系将当下文中证实。先来瞧怎么用,其实如将上边两截代码的
receive 替换成 react 即可:

class ActorItem extends Actor {
  def act(): Unit = {
    react { case msg => println(msg) }
  }
}

object Test {
  def main (args: Array[String]): Unit = {
    val actorItem = new ActorItem
    actorItem.start()

    actorItem ! "actor test1"
    actorItem ! "actor test2"
  }
}

输出:
actor test1

object Test {
  def main (args: Array[String]): Unit = {
    val actorItem = actor {
      react { case msg => println(msg) }
    }

    actorItem ! "actor test1"
    actorItem ! "actor test2"
  }
}

输出:
actor test1

络绎不绝处理音信

要你仔细观望,就会面发现点的每个示例中,都于 actor 发送了”actor
test1″和”actor test2″两久音信,但最终就打印了”actor
test1″这同样长长的消息。这是盖,不管是 receive 依旧 react,都但是由 mailbox
中获取一漫长音信举办拍卖,处理了事后休相会再一次取得一修处理。假若想要时时刻刻从 maibox
中撤信息并拍卖,也发一定量种植方法。

方式一:使用 loop。适用于扩大 Actor 和 actor 方法简单种植办法

class ActorItem extends Actor {
  def act(): Unit = {
    loop {
      react { case msg => println(msg) }
    }
  }
}

object Test {
  def main (args: Array[String]): Unit = {
    val actorItem = new ActorItem
    actorItem.start()

    actorItem ! "actor test1"
    actorItem ! "actor test2"
  }
}

输出:
actor test1
actor test2

方式二:在 receive 处理中调用receive;在 react 处理着调用
react。仅适用于 actor 方法那种措施

class ActorItem extends Actor {
  def act(): Unit = {
    react {
      case msg => {
        println(msg)
        act()
      }
    }
  }
}

object Test {
  def main (args: Array[String]): Unit = {
    val actorItem = new ActorItem
    actorItem.start()

    actorItem ! "actor test1"
    actorItem ! "actor test2"
  }
}

Actor是何等做事的

每个actor对象还生一个
mailbox,可以省略的以为是一个队,用来存放发送给那些actor的音讯。
当 actor 发送音信时,它并无相会堵塞,而当 actor
接收信息不时,它吗未会面为从断。发送的信在吸纳 actor 的 mailbox
中等待处理,直到 actor 调用 receive 方法。

receive 具体是怎工作的为?来瞧她的源码:

def receive[R](f: PartialFunction[Any, R]): R = {
  var done = false
  while (!done) {
    //< 从 mailbox 中取出一条消息
    val qel = mailbox.extractFirst((m: Any, replyTo: OutputChannel[Any]) => {
      senders = replyTo :: senders
      //< 与偏函数进行匹配,匹配失败返回 null
      val matches = f.isDefinedAt(m)
      senders = senders.tail
      matches
    })
    if (null eq qel) {
      //< 如果当前mailbox里面没有可以处理的消息,调用suspendActor,该方法会调用wait
      waitingFor = f.isDefinedAt  
      isSuspended = true 
      suspendActor()  
    } else {
      //< 执行到这里就说明成功从 mailbox 中获得匹配的消息
      received = Some(qel.msg)
      senders = qel.session :: senders
      done = true
    }
  }

  //< 成功获得消息后,调用 f.apply 来执行对应的操作
  val result = f(received.get)
  received = None
  senders = senders.tail
  result
}

如出一辙图胜千言,下图也 receive 模型工作流程

actor_receive.jpg

与线程的关联

Actor 的线程模型可以这么懂:在一个过程被,所有的 actor
共享一个线程池,总的线程个数可以配备,也可以依照 CPU 个数控制。

当一个 actor 启动后,Scala 分配一个线程给其拔取,要是用 receive
模型,这多少个线程就直接为该 Actor 所有。
假诺利用 react 模型,react 找到并处理信息继连无返,它的回来路也
Nothing,Scala 执行了 react 方法后,抛来很,调用 act 也就是是直接调用
react 的线程会捕获这些大,忘掉这多少个 actor,该线程就得为外actor
使用。
就此,假如会为此 react 就玩命接纳 react,可以省去线程。

良好的 Actor 风格

独自通过音讯以及 actor 通信

选举个例证,一个 GoodActor可能相会于发作于 BadActor
的信备受富含一个对自己之援,来注明作为音信源的和睦。假若 BadActor
调用了 GoodActor 的某任意的法子而非是经 “!”
发送音讯的语,问题即来了。被调用的道或者读到 GoodActor
的私有实例数据,而那个数据可能是出于外一个线程写进。结果是,你用确保
BadActor 线程对那些实例数据的读取和 GoodActor
线程对这么些数据的写入是一同于一个沿上之。一旦绕开了 actor
之间的消息传递机制,就赶回了共享数据与锁模型中。

优选不可变的音讯

出于 Scala 的 actor 模型提供了在每个 actor 的 act
方法被之单线程环境,不需担心在这办法的落实着以的对象是不是是线程安全的。

管音信对象是线程安全之极品途径是当消息备受行使不可变对象。任何只有 val
字段还这么些字段只援引到不可变对象的接近的实例都是不可变的。

如您发现自己有一个可变的目的,想继承以它,同时为想就此音信发送给此外一个
actor,此时应考虑造并发送它的一个副本,比如利用 clone 方法。

给音讯于包含

朝某 actor 发送消息,假使你想抱那一个 actor
的复,可以于信遭到蕴藏我。示例如下:

class ActorItem extends Actor {
  def act(): Unit = {
    react {
      case (name: String, actor: Actor) => {
        println( name )
        actor ! "Done"
      }
    }
  }
}

object Test {
  def main (args: Array[String]): Unit = {
    val actorItem = new ActorItem
    actorItem.start()

    actorItem ! ("scala", self)

    receive {
      case msg => println( msg ) 
    }
  }
}

输出:
scala
Done

应用样本类

于上例中,若把(name: String, actor: Actor)概念成类,代码可读性会大大提高

case class Info(name: String, actor: Actor)

class ActorItem extends Actor {
  def act(): Unit = {
    react {
      case Info => {
        println( Info.name )
        actor ! "Done"
      }
    }
  }
}

**传送门: **Scala
在简书目录


接关注我的微信公众号:FunnyBigData

FunnyBigData

相关文章