Eternity's Chit-Chat

aeternum.egloos.com



Thread Confinement Concept & Principle

누구나 익히 알고 있는 것처럼 실행할 때마다 정상적인 결과를 내놓는 프로그램을 개발하는 것은 매우 어려운 일이다. 그러나 실행할 때마다 정상적인 결과를 내놓는 멀티 스레딩 프로그램을 개발하는 것은 거의 기적에 가까운 일이다. 동시성(concurrency)은 확장성(scalability)과 단순성(simplicity), 효율성(efficiency)이라는 장점을 제공하는 한편 수많은 버그 더미 속에 프로그래머를 처박아 넣고 허우적거리게 만들 수도 있는 야누스적인 존재다.

 

동시성을 이야기할 때 수반되는 것이 안전성(safety)과 활동성(liveness)에 대한 논의다. 여러 스레드가 동시에 하나의 자원에 변경을 가할 때 그 결과가 예측한 상태라면, 즉 정확하다면 이 작업은 안전하다. 그러나 안전성을 확보하기 위해 여러 작업의 실행에 제약을 가할 경우 활동성을 저해할 수 있다. 동시성 설계의 핵심은 안전성을 확보하면서 활동성을 보장하는 것이다. 그리고 대부분의 엔터프라이즈 어플리케이션에서 안전성과 활동성을 보장하기 위해 채택하고 있는 기법이 바로 스레드 한정(Thread Confinement) 기법이다.

 

스레드 한정 기법의 핵심은 단순하다. 스레드 별로 필요한 객체를 생성해서 사용하고 스레드 간에 공유는 하지 말자는 것이다. 스레드 간에 객체를 공유할 필요가 없다면 동기화 코드를 작성할 필요도 없고 동기화로 인해 프로그래머가 짊어져야 할 짐도 줄어든다. 객체는 개별 스레드 안에서 생성되고 스레드가 종료되면 가비지 컬렉션에 의해 자동으로 제거된다.

 

스레드 한정 기법은 현재의 거의 모든 JEE 어플리케이션에서 사용하고 있는 기법이다. 서블릿 컨테이너는 HTTP 요청별로 하나의 스레드를 할당한다. 서블릿에 의해 호출되는 코드 내에서 명시적으로 객체를 생성하고 해당 객체를 스레드 간에 공유하지 않을 경우 이 객체는 스레드에 한정된다. 따라서 도메인 로직을 처리하기 위해 도메인 객체를 생성하거나 REPOSITORY DAO를 통해 도메인 객체를 로드한 후 다른 스레드와 공유하기 위한 별도의 처리를 하지 않고 있다면 스레드 한정 기법을 사용하고 있는 것이다.

 

스레드 한정 기법을 사용하여 객체를 관리할 경우 객체의 상태 변경을 관리하기가 쉽다. 오직 하나의 스레드에 의해서만 객체의 상태가 변경되기 때문에 동시성 문제를 신경 쓸 필요가 없기 때문이다.

 

스레드 한정 기법을 사용하는 대표적인 사례가 Struts2 Action이다. Struts2는 각 HTTP 요청 별로 새로운 Action을 생성하기 때문에 해당 Action 인스턴스는 하나의 스레드 범위로 한정되어 사용된다. 따라서 Struts2 Action에는 HTTP 요청 정보를 바인딩할 수 있으며 Action의 상태 변경 작업은 스레드에 안전하다. 반면 Struts1에서는 모든 스레드가 하나의 Action 인스턴스를 공유한다. Action이 하나의 스레드에 한정되지 않기 때문에 직접 HTTP 요청 정보를 바인딩할 수 없으며 따라서 Struts1에서는 Action Form이라는 별도의 객체에 요청 정보를 바인딩하는 방법을 사용한다. 여기에서 주목해야 할 점은 Action Form이 각 스레드 별로 생성된다는 점이다. , Struts1의 경우에는 Action Form에 스레드 한정 기법을 사용하고 있는 것이다. Struts2 Action Struts1의 스레드에 한정되는 Action Form과 스레드 간에 공유되는 Action이라는 두 가지 스레드 범위를 스레드에 한정되는 하나의 Action 객체로 통합하여 아키텍처를 단순화시키는 동시에 스레드 안전성을 효율적으로 유지하는 방법을 사용하고 있다.

 

스레드 한정 기법과 함께 단순한 동기화 방법의 양대 산맥으로 불리는 방법이 있으니 바로 불변 객체(Immutable Object)를 사용하는 방법이다. 불변 객체란 생성한 후 상태를 변경할 수 없는 객체를 의미한다. 불변 객체의 경우 다른 스레드에 의해 변경될 수 없기 때문에 스레드 간에 불변 객체를 공유하는 것은 안전하다.

 

그러나 불변 객체를 만드는 것이 항상 가능하지는 않다. 불변 객체는 생성 후 상태를 변경할 수 없기 때문에 생성 시에 필요한 모든 정보를 생성자의 인자로 넘겨 생성자 내부에서 상태를 초기화해야 한다. 그러나 모든 정보를 생성자로 넘기는 것이 불가능하거나 인자의 개수가 너무 많아져 인자의 순서를 기억하기도 어렵고 가독성을 저해하게 된다.

 

따라서 객체가 완전한 불변 객체는 아니더라도 여러 스레드에 의해 공유되기 전까지만 상태를 변경하고 스레드에 의해 공유되는 시점 이후에는 상태를 변경하지 않도록 내부적인 규약에 따르면 쉽게 스레드 안전성을 확보할 수 있다.

 

기술적으로만 본다면 특정 객체가 불변일 수 없다고 해도, 한 번 공개된 이후에는 그 내용이 변경되지 않는다고 하면 결과론적으로 봤을 때 해당 객체도 불변 객체라고 볼 수 있다. 이런 정도의 불변식이라고 하면 불변 객체를 정의하면서 살펴 봤던 여러 가지 요구 조건을 반드시 만족시켜야 할 필요는 없다. 대신 프로그램 내부에서 해당 객체를 한 번 공개한 이후에는 마치 불변 객체인 것처럼 사용하기만 하면 된다. 이와 같이 결과적인 불변 객체는 개발 과정도 훨씬 간편하고 동기화 작업을 할 필요가 없기 때문에 프로그램의 성능을 개선하는 데도 도움이 된다.

- Java Concurrency in Practice

 

불변 객체는 아니지만 초기화된 후 상태 변경이 발생하지 않는다는 해당 객체는 클라이언트에 종속되는 상태를 가지지 않는다는 것을 의미한다. , 이 객체들은 메소드 호출 간에 상태를 공유할 필요가 없으며 이를 상태 없음(Stateless)’이라고 표현한다. 따라서 상태 없는 객체들은 스레드에 안전하며 하나의 객체를 여러 스레드가 공유해서 사용할 수 있다. 대부분의 DEPENDENCY INJECTION을 제공하는 경량 프레임워크는 상태 없는 객체의 인스턴스를 하나만 생성할 수 있는 선언적인 방법을 제공한다. Spring의 경우 빈을 정의할 때 범위를 singleton으로 선언하면 빈 컨텍스트 내에 하나의 인스턴스만 유지하고 모든 클라이언트에게 동일한 빈을 제공한다.

 

<bean id="productRepository"

class="org.eternity.customer.memory.CollectionProductRepository"

scope="singleton">

<property name="registrar" ref="registrar"/>

</bean>

 

Spring은 빈의 초기화가 완료될 때까지 어떤 스레드도 해당 객체에 접근할 수 없음을 보장한다.  따라서 SETTER INJECTION을 사용하여 상태를 설정하는 객체라도 안전하게 초기화되었음을 보장할 수 있으며 생성 후에 어떤 스레드도 해당 객체를 수정하지 않도록 보장할 경우 스레드에 안전하다.

 

만약 객체가 상태 없는 객체가 아니라면, 즉 객체가 스레드 간에 공유될 때 임의의 스레드에 의해 상태 변경이 될 수 있다면 아예 스레드 별로 생성해서 사용하는 스레드 한정 기법을 사용하는 것이 좋다. Spring의 경우 빈의 범위를 prototype으로 설정하면 스레드 한정 기법을 사용할 수 있다.

 

<bean id="productRepository"

class="org.eternity.customer.memory.CollectionProductRepository"

scope="prototype">

<property name="registrar" ref="registrar"/>

</bean>

 

스레드 한정 기법과 불변 객체(또는 상태 없는 객체)는 상호 배타적인 기법이 아니다. 모든 프레임워크는 두 가지 기법을 적절하게 혼합해서 사용하고 있다. 대표적으로 Struts2의 경우 Action은 스레드 한정 기법을 사용하지만 Interceptor는 모든 스레드가 공유하는 상태 없는 객체이다. 따라서 Action을 사용하는 스레드는 자유롭게 Action의 상태를 변경할 수 있지만 Interceptor의 상태를 변경해서는 안된다.

 

잠시 눈을 감고 현재 작업중인 어플리케이션의 아키텍처를 떠올려 보자. 아키텍처의 어떤 부분에서 스레드 한정 기법을 사용하고 어떤 부분에서 상태 없는 객체를 사용하고 있는지 구분해 보자. 만약 구분이 명확하지 않다면 여러분이 작성한 코드는 스레드 안전하지 않을 가능성이 높다. 스레드 안전성을 유지하기 위한 방안은 아키텍처 레벨의 결정 사항이며 명확한 기술적인 가이드라인과 더불어 팀 내의 암묵적인 규율에 의해 지켜져야 할 중요한 영역이다. 만약 여러분의 작업 도메인이 명시적인 동기화 메커니즘을 필요로 하는 WAS, DB, 임베디드 소프트웨어와 같은 영역이 아닌 일반적인 엔터프라이즈 어플리케이션 영역이라면 동기화 메커니즘으로 스레드 한정 기법과 상태 없는 객체에 의존하고 있을 확률이 높다.


덧글

  • 떠돌이 2010/10/22 10:42 # 삭제

    내용 감사히 담아갑니다. 출처는 본문 상단에 표기 하였습니다.
  • 이터너티 2010/10/22 20:16 #

    예 내용이 도움이 되었으면 좋겠네요.
  • justant 2015/06/24 09:45 # 삭제

    정리된 내용 잘 보고 갑니다 ~! 감사합니다!
  • 이터너티 2015/06/25 00:44 #

    예 방문 감사드립니다. ^^
※ 로그인 사용자만 덧글을 남길 수 있습니다.