目录
摘要
定义 Actor,获取消息,发送消息,DSL 形式,Actor 实现机制,Actor 与线程,同步操作,ask,异步操作,并行集合,远程 Actor
Actor
概述
Actor
是 Scala 的并发模型。在2.10之后的版本中,使用Akka
作为其推荐的Actor
实现。Actor
是类似线程的实体,有一个邮箱。Actor
可以通过system.actorOf
来创建,receive
获取邮箱消息,!
向邮箱发送消息。
使用Actor
定义一个 Actor
import akka.actor.{Actor}
class EchoServer extends Actor {
def receive = {
case msg: String => println("echo " + msg)
}
@throws[Exception](classOf[Exception])
override def preStart(): Unit = {super.preStart(); println("preStart")}
}
使用 Actor
//通过默认构造器创建 Actor 对象
val system = ActorSystem()
val echoServer = system.actorOf(Props[EchoServer])
echoServer ! "hi"
system.shutdown
//--> output
//preStart
//echo hi
这个例子是一个 EchoServer,接受信息并打印。
简写形式
import akka.actor.ActorDSL._
import akka.actor.ActorSystem
implicit val system = ActorSystem()
val echoServer = actor(new Act {
become {
case msg => println("echo " + msg)
}
whenStarting {
println("preStart")
}
})
echoServer ! "hi"
system.shutdown
akka.actor.ActorDSL
中的 actor
函数可以接受一个 Actor
的构造器 Act
,启动并返回Actor
对象。
原理
- Actor 和线程是不同的抽象,他们的对应关系是由 Dispatcher 决定的。
- Actor 比线程轻量。在 Scala 中可以创建数以百万级的 Actor。奥秘在于 Actor 直接可以复用线程。
- Actor 和线程之间没有一对一的对应关系。一个 Actor 可以使用多个线程,一个线程也会被多个 Actor 复用。
import akka.actor.{ Actor, Props, ActorSystem }
import akka.testkit.CallingThreadDispatcher
implicit val system = ActorSystem()
class EchoServer(name: String) extends Actor {
def receive = {
case msg => println("server" + name + " echo " + msg +
" by " + Thread.currentThread().getName())
}
}
val echoServers = (1 to 10).map(x =>
system.actorOf(Props(new EchoServer(x.toString))
.withDispatcher(CallingThreadDispatcher.Id)))
(1 to 10).foreach(msg =>
echoServers(scala.util.Random.nextInt(10)) ! msg.toString)
system.shutdown
这个例子创建 4 个 Actor,每次调用的时候打印自身线程名称。
同步返回
Actor 非常适合于较耗时的操作。比如获取网络资源。
import akka.actor.ActorDSL._
import akka.pattern.ask
implicit val ec = scala.concurrent.ExecutionContext.Implicits.global
implicit val system = akka.actor.ActorSystem()
val versionUrl = "https://github.com/SidneyXu"
val fromURL = actor(new Act {
become {
case url: String => sender ! scala.io.Source.fromURL(url)
.getLines().mkString("\n")
}
})
val version = fromURL.ask(versionUrl)(akka.util.Timeout(5 * 1000))
version.foreach(println _)
system.shutdown
这个例子通过调用 ask
函数来获取一个 Future
。ask
内部也是用 !
来传递消息,但是其可以同时设置超时时间。
Future
像 Option
一样有很多高阶方法,可以使用 foreach
查看结果。
异步返回
异步操作可以最大发挥效能。Scala 的 Futrue
很强大,可以异步返回。
可以实现 Futrue
的 onComplete
方法。当 Futrue
结束的时候就会回调。
import akka.actor.ActorDSL._
import akka.pattern.ask
implicit val ec = scala.concurrent.ExecutionContext.Implicits.global
implicit val system = akka.actor.ActorSystem()
val versionUrl = "https://github.com/SidneyXu"
val fromURL = actor(new Act {
become {
case url: String => sender ! scala.io.Source.fromURL(url)
.getLines().mkString("\n")
}
})
val version = fromURL.ask(versionUrl)(akka.util.Timeout(5 * 1000))
version onComplete {
case msg => println(msg); system.shutdown
}
并行集合
val urls = List("http://scala-lang.org",
"https://github.com/SidneyXu")
def fromURL(url: String) = scala.io.Source.fromURL(url)
.getLines().mkString("\n")
val t = System.currentTimeMillis()
urls.map(fromURL(_))
println("time: " + (System.currentTimeMillis - t) + "ms")
这个例子是访问若干 URL,并记录时间。如果能并行访问,就可以大幅提高性能。
尝试将 urls.map
修改为 urls.par.map
,这样每个 map 中的函数都可以并发执行。
远程 Actor
服务端
代码
import akka.actor.{ActorSystem, Props}
object RemoteServer extends App {
implicit val system = ActorSystem("RemoteSystem")
val remoteActor = system.actorOf(Props[EchoServer], name = "remoteServer")
remoteActor ! "The RemoteActor is alive"
}
配置文件
配置文件需要放在 classpath
下, 系统默认读取的配置文件名为 application.conf
application.conf
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
transport = "akka.remote.netty.NettyRemoteTransport"
netty.tcp {
hostname = "127.0.0.1"
port = 5150
}
}
}
客户端
代码
object Client extends App {
implicit val system = ActorSystem("LocalSystem", ConfigFactory.load("client"))
val remote = system.actorSelection("akka.tcp://RemoteSystem@127.0.0.1:5150/user/remoteServer")
remote ! "Hello from the LocalActor"
}
配置文件
可以通过 ConfigFactory.load()
来读取指定的配置文件,文件名不包含后缀名。
只有服务器端需要知道端口号,所以客户端的端口号设为 0
。
client.conf
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
transport = "akka.remote.netty.NettyRemoteTransport"
netty.tcp {
hostname = "127.0.0.1"
port = 0
}
}
}
运行
先运行服务器端,控制台输出 “echo The RemoteActor is alive”,
再运行客户端,服务器端控制台会接着输出 “echo Hello from the LocalActor”