액터는 매우 가벼운 동시성 엔터티들이다. event-driven receive loop를 이용하여 비동기적 메시지를 처리한다.
동시 처리성을 높이려면 Dispatcher 를이용 ,분산처리 확장은 Cluster가 활용될수 있으며 로컬용으로 작성된 개발방법이 크게 달라지지 않습니다.
AKKA는 액터를 전반적으로 활용하기때문에 액터패턴에 대한 컨셉을 먼저 살펴보겠습니다.
간단한 용어설명
- Active Object : 항상 작동중이고 명령 가능한 능동적 객체란 점에서 액터 패턴과 유사
- 명령가능 : 액터는 메시지를 통해서만 명령이전달된다는 점에서 차이가있습니다.
- 비동기 : 다른 녀석에게 일을 넘기고 내일을 한다. ( 논블럭킹 )
- 동기 : 다른 녀석이 일을 마칠때까지 기다린다. ( 블럭킹 )
- 능동적 : 나만의 큐와 스케쥴러를 가지고 있음
- 패턴 : 이하 참고
OOP와 비교해본 Actor Model
OOP(Object-oriented programming)
public class DeepThought { private int state; public int State { get { lock (this) { Thread.Sleep(3000); //나의 상태에대한 깊은 생각에 빠짐..나는누군가? 여긴어딘가? return state; } } set { lock (this) { //깊은 생각에 빠질땐 누구도 건들지 말기를 바람,그대가 최고우선순위 스레드라할지라도 state = value; } } } } var dt = new DeepThought() Thread.new { console.log( dt.State ) } Thread.new { dt.State=1 }
자신의 상태변경및 제공을 멀티스레드에 안전한 방식으로 OOP방식으로 설계한다고 가정하자
이 클래스는 이야기한다, '자신의 속성을 Lock(metex)를 걸어 , 누군가 나를 접근하고 있을때, 해당 오브젝트를 건들지마(또는 기달려야해)' 라고 이야기하고 있다.
일반적으로 OOP 관점에서는 스레드 세이프하다란 의미는 메스드사용시 공유객체를 Lock을 걸거나? 별도의 독립된 객체를 추가로 생성하여
동시처리,동시접근에대한 문제를 풀려고 한다. 공통점은 특정한 속성값이 멀티쓰레드에서 동시접근 제한 방지를 위해 공유가된다는 점이다.
==> Object1이 먼저 Object2를 접근하고 Lock을걸기때문에, Object3는 Object1의 메서드A 가 끝날때까지 Object3는 대기상태에빠짐
접근공유문제와 동시성 문제 해결하기위해, 복잡한 쓰레드 모델을(CriticalSection,SpinLock,Mutex,Semaphore) 익혀야하며
클러스터 구성하기도 전에, 단일노드에서 효율적 동시성 접근문제에 대해 많은 고민을 해야합니다.
Actor
액터의 기본철학은, 액터가 보유하고 있는 상태 메모리 공간는 어느 액터 혹은 심지어 부모가 액터가 자식 액터의 속성도 접근할수 없습니다.
( Protected 는 자식이 접근할수 있다란 OOP의 상속 개념을 사용할수 없습니다.)
다시말해 액터내부에 일어나는 일은 어느 누구와도 '공유'되지 않으며 죽음의 칵테일에서 공유라는 속성을 제거함으로 멀티쓰레드와 관련된 문제의 대부분을 제거 하고 시작한다.
공유되지 않기때문에, 임계영역 처리를 위해 'lock' 'synchronized' 와 같은 부자연스러운 키워드가 필요없으며 어려운 스레드 모델을 사용해야할 필요가 없어집니다.
메시지를 통해서만 상대에게 명령할수 있고 값을 알아낼수 있다란 단순한 방법만을 사용합니다.
예제를 통해 어떻게 작동되는지 살펴봅시다.
public class MyActor: ReceiveActor { //Actor모델 에서는 안전한 동시성처리를 위해 어떠한 프로퍼티(속성)도 공유가 필요없음으로, 속성 접근제한에 priave만으로 충분합니다. private readonly ILoggingAdapter log = Context.GetLogger(); private int state =0; public MyActor() { Receive<string>(message => { log.Info("Received String message: {0}", message); Sender.Tell(state ); }); Receive<SomeMessage>(message => {...}); } } //코드 변경 최소로 옵션만으로 로컬,원격,클러스터등러등의 작동 선택 전략이 가능하다. var remoteActor = system.ActorSelection("akka.tcp://MyServer@127.0.0.1:8001/user/someActor"); // 질의를 하던지?(Ask), 단지 말만한다던지?(Tell), 결과를 기다리던지(Result)? // 객체 접근 기반이아닌 메시지 전송기반의 인터페이스를 사용하는 조금더 추상화된 모델이다. // Tell의 의미를 상세히 분석하면, 내가 한말에 결과값이 필요없으니 처리만해달라란 의미이며 // 네트워크상의 의미로 수행결과가 있어도 어떠한 값을 리턴한다고 해도, 결과메시지 자체가 발송되지 않습니다. var state = remoteActor.Ask("Hello").Result;
Actor는 오로지 메시지만 주고 받으며 동시성을 달성하며, 결과값을 얻기위해서는 객체접근이아닌 질의를 해야한다.
이와같은 처리방식은 동일한 컨셉으로 로컬뿐아니라 리모트처리,분산처리등으로 확장이 될수가있다.
메시지 전송에의해서만 처리를 하게되며, 이러한 방식은 리모트로의 전환시 구현체의 변경이 없음을 의미합니다.
액터는 메시징을 받는 스레드와,이것을 하니씩 꺼내에 순차적으로 처리하는 작업 스레드가 분리되어 있음으로
연속성을 보증함과 동시에, 작업이 블락킹으로 구현 되었다고 하더라도, 항상 메시지를 받을수 있습니다.
Actor2는 그 누구와도 자신의 상태에대한 메모리를 공유하지 않기때문에 상태변경에 필요한 속성에 Lock이 필요가 없습니다.
액터의 특징을 살펴 보고자 반대편에 있는 OOP를 이용한 스레드 직접 제어의 단점을 예를 들었지만
액터 시스템도 Dispatcher 란 튜닝가능 객체가 스레드 풀을 이용을 하며, OOP로 작성된 추상화 객체입니다.
도메인 메시지 처리에 있어 스레드를 이용하는 OOP적 설계방식이 단순성을 유지하고 확장에 유연한 개발능력을 가지고 있는가?
스스로 질문을 던저볼수 있으며, 메시징 처리에 있어 동시성 처리를 포함한 다음 3가지 주제는 액터나 아카와 상관없는
공통주제로 , 액터를 연마하기전 추천 선행 자료입니다.