최근 수정 시각 : 2022-09-12 23:03:04

행위자 모델


[[컴퓨터공학|컴퓨터 과학 & 공학
Computer Science & Engineering
]]
[ 펼치기 · 접기 ]
||<tablebgcolor=#fff,#1c1d1f><tablecolor=#373a3c,#ddd><colbgcolor=#0066DC><colcolor=white> 기반 학문 ||수학(해석학 · 이산수학 · 수리논리학 · 선형대수학 · 미적분학 · 미분방정식 · 대수학(환론 · 범주론) · 정수론) · 이론 컴퓨터 과학 · 암호학 · 전자공학 · 언어학(형태론 · 통사론 · 의미론 · 화용론 · 음운론) · 인지과학 ||
하드웨어 구성 SoC · CPU · GPU(그래픽 카드 · GPGPU) · ROM · RAM · SSD · HDD · 참조: 틀:컴퓨터 부품
기술 기계어 · 어셈블리어 · C/C++ · C# · Java · Python · BIOS · 절차적 프로그래밍 · 객체 지향 프로그래밍 · 해킹 · ROT13 · 일회용 비밀번호 · 사물인터넷 · 와이파이 · GPS · 임베디드 · 인공신경망 · OpenGL · EXIF · 마이크로아키텍처 · ACPI · UEFI · NERF · gRPC · 리버스 엔지니어링 · HCI · UI · UX · 대역폭 · DBMS · NoSQL · 해시(SHA · 브루트 포스 · 레인보우 테이블 · salt · 암호화폐) · RSA 암호화 · 하드웨어 가속
연구

기타
논리 회로(보수기 · 가산기 · 논리 연산 · 불 대수 · 플립플롭) · 정보이론 · 임베디드 시스템 · 운영 체제 · 데이터베이스 · 프로그래밍 언어{컴파일러(어셈블러 · JIT) · 인터프리터 · 유형 이론 · 파싱 · 링커 · 난해한 프로그래밍 언어} · 메타데이터 · 기계학습 · 빅데이터 · 폰노이만 구조 · 양자컴퓨터 · 행위자 모델 · 인코딩(유니코드 · MBCS) · 네트워크 · 컴퓨터 보안 · OCR · 슈퍼컴퓨터 · 튜링 머신 · FPGA · 딥러닝 · 컴퓨터 구조론 · 컴퓨터 비전 · 컴퓨터 그래픽스 · 인공지능 · 시간 복잡도(최적화) · 소프트웨어 개발 방법론 · 디자인 패턴 · 정보처리이론 · 재귀 이론 · 자연어 처리(기계 번역 · 음성인식) · 버전 (버전 관리 시스템 · Git · GitHub)

1. 개요2. 예제
2.1. Scala + Akka
3. 순수 이론으로서의 양가성4. 관련 개념
4.1. CSP

1. 개요

Actor Model(이하 행위자 모델)은 Actor(이하 행위자)를 병행 연산(Concurrent Computing)의[1] 범용적 기본 단위로 삼는 모델이다. 칼 휴잇(Carl Hewitt) 등이 MIT 인공지능 연구소와 미 해군 연구국의 지원을 받아 1973년에 작성한 논문[2]에서 최초로 정식화한 것으로 여겨지고 있다.

행위자는 아래의 행위를 할 수 있다.
  • 새로운 행위자를 만든다
  • 자신 혹은 다른 행위자에게 메시지를 발송한다
  • 다음에 수신할 메시지에 대해 취할 행위를 정한다

행위자 모델에서 행위자들은 메시지를 주고 받을 뿐이므로, 연산 단위들간의 가변 상태 공유를 허용하는 병행 모델들의 고질적인 문제점인 교착 상태, 경쟁 상태 등의 발생 가능성이 낮다.

Erlang, Elixir[3] 그리고 Scala[4] 등과 같은 프로그래밍 언어들이 행위자 모델에 기초해 병행성 기능을 제공한다.

에릭 마이어(Erik Meijer)가 묻고 칼 휴잇이 답하는 행위자 모델

2. 예제

2.1. Scala + Akka

#!syntax java
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, 메시지/행위, 내부값)하며 원자성(原子性)을 갖고, 행위자들 상호간은 평등하며 위계로부터 자유롭다. 객체지향 개념이 다양한 변위와 실험을 거치면서 이론의 차원을 넘어 상업적 성공을 거둔 것에 비춰 보면 무중복성과 효율성, 유지보수의 용이성 등을 담보하기 위한 조직화 및 추상화의 동기가 존재한다.

4. 관련 개념

4.1. CSP

별도의 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라는 별도의 프로젝트로 분리되었다