1. 개요
Actor Model(이하 행위자 모델)은 Actor(이하 행위자)를 병행 연산(Concurrent Computing)의[1] 범용적 기본 단위로 삼는 모델이다. 칼 휴잇(Carl Hewitt) 등이 MIT 인공지능 연구소와 미 해군 연구국의 지원을 받아 1973년에 작성한 논문[2]에서 최초로 정식화한 것으로 여겨지고 있다.
행위자는 아래의 행위를 할 수 있다.
행위자는 아래의 행위를 할 수 있다.
- 새로운 행위자를 만든다
- 자신 혹은 다른 행위자에게 메시지를 발송한다
- 다음에 수신할 메시지에 대해 취할 행위를 정한다
2. 예제
2.1. Scala + Akka
object Example {
import akka.actor.{Actor, ActorRef, Props}
object MapReduce {
lazy val sys = akka.actor.ActorSystem()
def apply[A, B, C](map: A => B, nrOfMapActors: Int,
reduce: (C, B) => C, state: C,
io: C => Unit) =
sys.actorOf(Props(
classOf[MapReduce[A, B, C]],
map, nrOfMapActors,
reduce, state,
io))
}
class MapReduce[A, B, C](map: A => B, nrOfMapActors: Int,
reduce: (C, B) => C, state: C,
io: C => Unit) extends Actor {
import context.actorOf
lazy val ioActor = actorOf(Props(classOf[IO[C]], io))
lazy val reduceActor =
actorOf(Props(classOf[Reduce[B, C]], reduce, state, ioActor))
lazy val mapActor =
if (nrOfMapActors < 2)
actorOf(Props(classOf[Map.Single[A, B]], map, reduceActor))
else
actorOf(Props(classOf[Map.Multiple[A, B]], map, nrOfMapActors, reduceActor))
def receive = {
case elems: Seq[A] =>
for (elem <- elems) mapActor ! Message(elem)
}
}
object Map {
class Single[A, B](map: A => B, reduceActor: ActorRef) extends Actor {
def receive = {
case msg: Message[A] =>
reduceActor ! Message(map(msg.contents))
}
}
class Multiple[A, B](map: A => B,
nrOfActors: Int,
reduceActor: ActorRef) extends Actor {
import akka.routing.{Router, RoundRobinRoutingLogic, ActorRefRoutee}
import akka.actor.Terminated
import context.{watch, actorOf}
var router = Router(
RoundRobinRoutingLogic(),
for (_ <- 1 to nrOfActors)
yield ActorRefRoutee(single)
)
def single =
watch(actorOf(Props(classOf[Single[A, B]], map, reduceActor)))
def receive = {
case msg: Message[A] =>
router route (msg, reduceActor)
case Terminated(one) =>
router = (router removeRoutee one) addRoutee single
}
}
}
class Reduce[A, B](reduce: (B, A) => B,
var state: B,
ioActor: ActorRef) extends Actor {
def receive = {
case msg: Message[A] =>
state = reduce(state, msg.contents)
ioActor ! Message(state)
}
}
class IO[T](io: T => Unit) extends Actor {
def receive = {
case msg: Message[T] => io(msg.contents)
}
}
case class Message[T](contents: T)
}
object Main extends App {
type II = (Int, Int)
val map: Seq[Int] => II = _.foldLeft (0, 0) {
(acc, n) => (acc._1 + 1, acc._2 + (n & 1)) }
val reduce: (II, II) => II = {
case ((counted, odds), (c, o)) => (counted + c, odds + o) }
val io: II => Unit = {
case (counted, odds) => println(s"Counted: $counted Odds: $odds") }
val nums = {
val rng = new java.security.SecureRandom
Stream.fill(10000000){rng.nextInt()}.grouped(100).toSeq }
val mapReduce = Example.MapReduce(map, 8, reduce, (0, 0), io)
mapReduce ! nums
}
3. 순수 이론으로서의 양가성
행위자의 메시지 발송 행위는 비동기적이며, 수신되는 메시지의 순서 또한 확정적이지 않다. 많은 경우 의미있는 전체로서의 구조를 만들기 위해서는 이러한 비동기성과 불확정성에 대한 조율이 필요하다.
행위자 각각은 단순(FSM, 메시지/행위, 내부값)하며 원자성(原子性)을 갖고, 행위자들 상호간은 평등하며 위계로부터 자유롭다. 객체지향 개념이 다양한 변위와 실험을 거치면서 이론의 차원을 넘어 상업적 성공을 거둔 것에 비춰 보면 무중복성과 효율성, 유지보수의 용이성 등을 담보하기 위한 조직화 및 추상화의 동기가 존재한다.
행위자 각각은 단순(FSM, 메시지/행위, 내부값)하며 원자성(原子性)을 갖고, 행위자들 상호간은 평등하며 위계로부터 자유롭다. 객체지향 개념이 다양한 변위와 실험을 거치면서 이론의 차원을 넘어 상업적 성공을 거둔 것에 비춰 보면 무중복성과 효율성, 유지보수의 용이성 등을 담보하기 위한 조직화 및 추상화의 동기가 존재한다.
4. 관련 개념
4.1. CSP
[1] 동시 연산이라고도 부르며, 한국에서는 concurrent와 parallel 둘 다 서로 같은 사전적 의미를 지니지만 컴퓨터계에서는 병렬을 가리키는 parallel과는 엄연히 다른 의미의 연산 형태이다.[2] Carl Hewitt, Peter Bishop, Richard Steiger (1973) - A Universal Modular ACTOR Formalism for Artificial Intelligence[3] Erlang VM에 기반하고 있으며 병행성에 대한 생각을 공유한다.[4] 정확하게는 Scala 2.11.0 버전부터 Akka라는 별도의 프로젝트로 분리되었다