Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Scala의 동시성(Concurrent) 프로그래밍

Java에서는 쓰레드 세이프하게 구현하기 위해서 synchronized를 사용하지만 Mutable 객체를 이용해서 구현하는 것은 쉽지 않습니다만 Functional 프로그래밍 스타일에서는 Immutalbe객체를 주로 사용합니다. 스칼라의 동시성 모델은 Immutability에 기반하며 Immutable객체는 다음과 같은 특징이 있습니다.

  • 바꿀수 있는 상태가 없기 때문에 쓰레드간에 자유롭게 공유할 수 있으며 동기화할 필요가 업어서 쓰레드-세이프합니다.
  • 복잡한 상태전이가 없기 때문에 간단하게 동작합니다.
  • 애플리케이션내에서 공유되고 재사용될 수 있습니다.
  • 객체의 상태가 수정될 걱정이 없기 때문에 더 적은 오류가 발생합니다.



Actor를 사용한 동시성
Actor는 이벤트기반의 경량 쓰레드를 제공하고 scala.actors.Actor 컴페니언 객체에 있는 function value나 closure를 파라미터로 받는 actor() 메서드를 통해서 액터를 생성할수 있습니다.


import scala.actors.Actor._

def example(n: Int) = {  n * 2 }

def testActor() = {
    val caller = self
    var result = ""

    for (i <- 10 to 11) {  
        actor { caller ! example(i) }
    }

    for (i <- 10 to 11) {  
        receive { case msg: Int => result += msg + ", " }  
    }

    "Received: " + result
}

testActor() // Received: 22, 20,

Actor에 메시지를 보내기 위해서는 !() 메서드를 사용하고 액터로부터 메시지를 받기위해서는 receive() 메서드를 사용하면 됩니다.

각 액터는 inputChannel[Any]로부터 인풋을 받고 outputChannel[Any]를 통해서 아웃풋을 보내는 자신만의 메시지큐를 가지고 있어서 보내진 메시지는 메시지큐에 순차적으로 저장되고 receive를 통해서 메시지큐에서 메시지를 꺼낼 수 있습니다. 액터는 메시지를 보낼때는 블락되지 않고 receive()를 호출할때는 블락됩니다. 메시지를 주고 받는 것은 기본적으로 비동기적이지만 동기적으로 사용하기를 원한다면 caller !? (500, "msg")처럼 !?() 메서드를 타임아웃 파라미터와 함께 사용해야 합니다. 메시지를 보낸 Actor는 sender로 참조할 수 있고 reply()를 통해서 sender에게 메세지를 보낼 수 있고 다른 액터로 메시지를 보낼때는 send()를 사용합니다.

Actor 트레이트를 사용해서 객체를 생성하면 더 명시적인 제어를 할 수 있습니다. 트레이트를 믹스인한 객체에 act()의 세부구현을 정의할 수 있고 start()와 exit() 메서드를 통해서 액터의 시작과 종료를 제어할 수 있습니다.



receive와 receiveWithin, react와 reactWithin
receive() 메서드는 function value나 closure를 받고 메시지처리의 응답을 리턴하지만 호출되면 응답을 받을때까지 블락됩니다. 타임아웃을 receiveWithin()을 사용하면 타임아웃까지만 기다리게 할 수 있습니다.
receive()와는 다르게 react()는 결과를 리턴하지 않습니다. react()를 호출하면 쓰레드풀로부터 계속 새로운 쓰레드를 받아서 계속 사용하며 호출된 쓰레드가 즉시 해제되기 때문에 react()이후에 작성된 코드는 실행되지 않습니다. 대신 receiveWithin()을 사용하면 할당된 쓰레드를 계속해서 사용합니다.

reactWithin()내에서 재귀호출하는 대신에 loop()내에서 reactWithin()을 호출하여 메시지를 받은 후 case문을 실행하고 다시 loop()내의 블락을 재실행됩니다. loopWhile()은  loop()와 동일하지만 파라미터로 넘긴 표현식이 유효한 경우에만 루프를 돌게 됩니다.



Receive메서드들 사이에서의 선택
receive(), receiveWithin(), react(), reactWithin()중에서 일반적으로는 Within이 있는 메서드를 사용하는 것이 좋습니다.  일반적으로는 액터로부터 받는 메시지가 워크플로우상의 실행 중심에 있다면 receiveWithin()을 사용하는 것이 낫고 메시지를 받는 서비스를 위해서 빠르게 수행후 응답해야 한다면 기다리는 동안 쓰레드를 붙잡고 있지 않은 reactWithin()이 낫습니다. 아니면 reactWithin()를 먼저 사용후 기능이 부족하면 receiveWithin()을 사용하는 식으로 접근할 수 있습니다.
2010/08/20 04:19 2010/08/20 04:19