如何在没有Akka的情况下实现简单的演员?对于许多(非固定计数)演员实例,绿线程,IoC(生命周期,基于道具的工厂,ActorRef&#),监督,背压等,我不需要高性能。只需要顺序性(队列)+处理程序+状态+消息传递。
作为副作用,我实际上需要基于小型演员的管道(使用递归链接)+一些并行演员来优化DSP算法计算。它将在没有传递依赖的库内部,因此我不想(并且不能像它的jar插件一样)推动用户创建并传递akkaSystem
,库应该拥有尽可能简单轻巧的界面。我不需要IoC,因为它只是一个库(一组函数),而不是一个框架 - 因此它比结构更具算法复杂性。但是,我认为actor是描述协议的好工具,我实际上可以将算法分解为少量的异步交互实体,因此它符合我的需要。
为什么不Akka
Akka很重,这意味着:
jstack
/ jconsole
/ jvisualvm
调试akka的绿色线程可能更难,因为一个角色可以在任何线程上执行。当然,Akka的jar(1.9Mb)和内存消耗(每GB 250万个演员)根本不重,所以你甚至可以在Android上运行它。但是也知道你应该使用专门的工具来观察和分析用户可能不熟悉的演员(如Typesafe Activator / Console)(并且我不会推动他们学习它)。它对企业项目来说都很好,因为它几乎总是有IoC,一些专业工具和连续迁移,但对于一个简单的库来说这不是一个好方法。
P.S。关于依赖关系。我没有它们,我不想添加任何东西(我甚至避免使用scalaz,这实际上适合这里),因为它会导致大量维护 - 我&#39 ;我必须让我的简单图书馆与Akka保持同步。
答案 0 :(得分:9)
这是JVM世界中最简单高效的演员,其基于来自Viktor Klang的极简主义Scala演员的API: https://github.com/plokhotnyuk/actors/blob/41eea0277530f86e4f9557b451c7e34345557ce3/src/test/scala/com/github/gist/viktorklang/Actor.scala
使用方便且安全,但在消息接收方面不是类型安全的,并且无法在进程或主机之间发送消息。
主要特点:
最简单的类似FSM的API,只有3种状态(Stay
,Become
和Die
):https://github.com/plokhotnyuk/actors/blob/41eea0277530f86e4f9557b451c7e34345557ce3/src/test/scala/com/github/gist/viktorklang/Actor.scala#L28-L30
简约错误处理 - 只需正确转发到执行程序线程的默认异常处理程序:https://github.com/plokhotnyuk/actors/blob/41eea0277530f86e4f9557b451c7e34345557ce3/src/test/scala/com/github/gist/viktorklang/Actor.scala#L52-L53
快速异步初始化需要大约200 ns完成,所以不需要额外的期货/演员来进行耗时的演员初始化:https://github.com/plokhotnyuk/actors/blob/41eea0277530f86e4f9557b451c7e34345557ce3/out0.txt#L447
最小内存占用,即处于被动状态的约40个字节(BTW new String()
在JVM堆中花费相同的字节数):https://github.com/plokhotnyuk/actors/blob/41eea0277530f86e4f9557b451c7e34345557ce3/out0.txt#L449
消息处理效率非常高,4核CPU的吞吐量为~90M msg / sec:https://github.com/plokhotnyuk/actors/blob/41eea0277530f86e4f9557b451c7e34345557ce3/out0.txt#L466
信息发送/接收非常有效,延迟时间约为100 ns:https://github.com/plokhotnyuk/actors/blob/41eea0277530f86e4f9557b451c7e34345557ce3/out0.txt#L472
每个参与者通过批处理参数调整公平性:https://github.com/plokhotnyuk/actors/blob/41eea0277530f86e4f9557b451c7e34345557ce3/src/test/scala/com/github/gist/viktorklang/Actor.scala#L32
有状态计数器的示例:
def process(self: Address, msg: Any, state: Int): Effect = if (state > 0) {
println(msg + " " + state)
self ! msg
Become { msg =>
process(self, msg, state - 1)
}
} else Die
val actor = Actor(self => msg => process(self, msg, 5))
结果:
scala> actor ! "a"
a 5
scala> a 4
a 3
a 2
a 1
答案 1 :(得分:4)
这将使用FixedThreadPool(以及其内部任务队列):
import scala.concurrent._
trait Actor[T] {
implicit val context = ExecutionContext.fromExecutor(java.util.concurrent.Executors.newFixedThreadPool(1))
def receive: T => Unit
def !(m: T) = Future { receive(m) }
}
大小为1的FixedThreadPool可以保证顺序性。当然,如果你需要100500个动态创建的actor,它不是管理线程的最佳方式,但是如果你需要一些固定数量的actor来实现你的协议,那就没问题。
用法:
class Ping(pong: => Actor[Int]) extends Actor[Int] {
def receive = {
case m: Int =>
println(m)
if (m > 0) pong ! (m - 1)
}
}
object System {
lazy val ping: Actor[Int] = new Ping(pong) //be careful with lazy vals mutual links between different systems (objects); that's why people prefer ActorRef
lazy val pong: Actor[Int] = new Ping(ping)
}
System.ping ! 5
结果:
import scala.concurrent._
defined trait Actor
defined class Ping
defined object System
res17: scala.concurrent.Future[Unit] = scala.concurrent.impl.Promise$DefaultPromise@6be61f2c
5
4
3
2
1
0
scala> System.ping ! 5; System.ping ! 7
5
7
4
6
3
5
2
res19: scala.concurrent.Future[Unit] = scala.concurrent.impl.Promise$DefaultPromise@54b053b1
4
1
3
0
2
1
0
此实现使用两个Java线程,因此它比没有并行化的计数“快两倍”。