나무모에 미러 (일반/어두운 화면)
최근 수정 시각 : 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라는 별도의 프로젝트로 분리되었다