Future 패턴
문맥 : 쓰레드(Client)가다른 쓰레드에 처리를 의뢰하고 있어 Client는 처리의 실행 결과도 얻고 싶다고 합시다.
문제 : 처리를 의뢰한 때에 실행 결과를 기다리고 있어 응답성이 저하되어 버리고 맙니다.
해결법
우선 처리 결과와 같은 인터페이스(API)를 가진 Future를 만듭시다. 그리고 처리 개시의 시점에서 Future를 반환값으로 합니다. 처리 결과는 나중에 Future에 set합니다. 이렇게 하면 Client는Future를 경유해서 처리 결과를 좋아하는 타이밍으로 얻는(기다리는)것이 가능합니다. 이것이 Future 패턴입니다.
관련
Client가 처리 결과를 기다리는 부분에서는 Guarded Suspention 패턴을 사용합니다.
Future 패턴은 Thread-Per-Message 패턴으로 처리 결과를 얻고 싶을 때 사용합니다.
Future 패턴은 Worker Thread 패턴으로 처리 결과를 얻고 싶을 때에 사용합니다.
Worker Thread 패턴
별명 : Thread Pool, Background Thread
문맥 : 쓰레드(Client)가 인스턴스(Host)의 메소드를 호출하고 있다고 합시다.
문제
메소드의 처리에 시간이 걸리면 응답성이 낮아지게 됩니다. 응답성을 높이기 위해서 새로운 쓰레드를 기동해서 메소드를 처리시키면 쓰레드 기동의 시간만큼 스루풋은 저하됩니다. 또 다수의 리퀘스트를 내면 다수의 쓰레드가 기동하게 되어 버려서 용량을 저하시킵니다.
해결법
우선 처리를 실행하는 쓰레드(워커 쓰레드)를 사전에 기동해 둡시다. 그리고 리퀘스트를 표현하는 인스턴스를 워커 쓰레드에 건네는 것입니다. 그렇게 하면 새로운 쓰레드를 매회 기동할 필요가 없게 됩니다.
관련
워커 쓰레드의 처리 결과를 호출하는 측에 건내줄 때에는 Future 패턴을 사용합니다.
리퀘스트를 표현하는 인스턴스를 워커 쓰레드에 건내줄 떼에는 Producer-Consumer 패턴을 사용합니다.
Thread_Per_Message 패턴
문맥 : 쓰레드(Client)가 인스턴스(Host)의 메소드를 호출하고 있다고 합시다.
문제 : 메소드의 처리가 끝나기까지 Host에서 제어가 되돌아오지 않습니다. 메소드의 처리에 시간이 걸리면 응답성이 저하됩니다.
해결법
Host 중에 새로운 쓰레드를 기동합시다. 그리고 메소드가 해야 할 실제의 처리는 이 새로운 쓰레드에 맡기는 것입니다. 이것으로 Clinet 쓰레드는 곧장 다음의 처리로 넘어 갈 수 있습니다. 이렇게 되면 Client 코드는 그대로 있고 응답성을 높일 수 있습니다.
구현
자바에서는 손쉽게 쓰레드를 기동하기 위해서 익명 내부 클래스를 이용할 수 있습니다.
new Thread() {
public void run() {
helper.handle(count, c);
}
}.start();
관련
쓰레드의 기동에 걸리는 시간을 줄이고 싶을 때에는 Work Thread 패턴을 사용합니다.
처리의 결과를 Client에 되돌리고 싶을 때에는 Future 패턴을 사용합니다.
Single Threaded Execution 패턴(Immutable 패턴)
문맥 : 복수의 쓰레드가 인스턴스를 공유하고 있지만 인스턴스의 상태가 변화하는 것은 아닙니다.
문제 : Single Threaded Execution 패턴을 사용하면 스루풋이 떨어져버립니다.
해결법
인스턴스가 만들어진 후 상태가 변화하는 것이 아니라면 Single Threaded Execution 패턴을 사용하는 것은 그만둡시다. 실수로 상태가 변화하는 코드를 쓰지 않도록 하기 위해 쓰레드가 필드를 변경하지 못하도록 합니다. 또 인스턴스의 상태를 변경하는 메소드(setter)가 있으면 소거합니다. 인스턴스의 상태를 알아보는 메소드(getter)는 있어도 상관없습니다.
이것이 Immutable 패턴입니다. Immutable 패턴을 사용하면 스루풋은 향상됩니다. 그러나 불변성(immutability)을 계속 보유하는 것은 곤란합니다. 문서에 그 클래스가 immutable인 것을 확실히 알아둡시다.
구현
자바에서는 필드를 은폐하기 위해 private를 사용합니다. 또한 변경할 수 없도록 하기 위해 final을 사용합니다.
관련
복수의 쓰레드를 배타 제어할 때에는 Single Threaded Execution 패턴을 사용합니다.
변경하는 쓰레드가 참조하는 쓰레드의 수보다 적을 때에는 Read-Write Lock 패턴을 사용 합니다.
Read-Write Lock 패턴
별명 : Reader Writer, Reader/Writer Lock, Readers/Writer Lock
문맥 : 복수의 쓰레드가 인스턴스를 공유하고 있어 인스턴스의 상태를 참조하는 것뿐인 쓰레드(Reader)와 변경하는 쓰레드(Writer)가 존재하고 있다고 합시다.
문제 : 쓰레드 간에 배타 제어를 하지 않으면 안정성을 잃게 됩니다. 그러나 Single Threaded Execution 패턴을 사용하면 스루풋이 떨어져 버립니다.
해결법 : 우선 『Reader를 제어하는 락』과 『Writer를 제어하는 락』을 나누어 이 두종류의락을 제공하는 ReadWriteLock을 도입해 봅시다. ReadWriteLock은『Writer 끼리 』및 『Reader와 Writer』의 배타 제어를 합니다. 『Reader 끼리』는 충돌해도 안정성에 영향을 주지 않기 때문에 배타 제어는 하지 않습니다. 이것으로 안정성을 잃어 버리지 않고 스루픗을 향상 시킬 수 있습니다. 이것이 Read-Write Lock 패턴이니다.
구현 : 자바에서는 finally를 사용하면 락의 해제를 잊어버리는 것을 방지할 수 있습니다.
관련 : Read-Write Lock 패턴의 ReadWriteLock이 배타 제어를 실현하는 부분에서는 Guarded Suspension 패턴을 사용합니다.
Writer가 전혀 존재하지 않을 때에는 Immutable 패턴을 사용합니다.
Producer-Consumer 패턴
문맥 : 어떤 쓰레드(Producer)에서 다른 쓰레드(Consumer)에 데이터를 넘겨준다고 합시다.
문제 : Producer 와 Consumer의 처리 속도가 다르면 늦은 쪽이 빠른쪽의 다리를 잡아끌어서 스루풋이 떨어진다. 또 Producer가 데이터를 쓸 때 동시에 Consumer가 데이터를 읽으려고 하면 안정성이 떨어진다.
해결법
Producer와 Consumer의 사이에 중간 지점이 되는 Channel을 준비합시다. 그리고 Channel에 복수의 데이터를 보유시킵니다. 그렇게 하면 Producer와 Consumer의 처리 속도 차를 완화할 수 있습니다. 또 Channel중에서 쓰레드의 배타 제어를 하면 데이터의 안정성도 잃어버리지 않습니다. 이것으로 스루풋을 떨어뜨리지 않고 더군다나 복수 쓰레드 간에 안전하게 데이터를 주고받을 수 있습니다.
이것이 Producer-Consumer 패턴입니다.
관련
Channel이 데이터를 안전하게 주고받는 부분에서는 Guarded Suspension 패턴을 사용합니다.
Future 패턴에서 반환값을 건넬 때에는 Producer-Consumer 패턴을 사용합니다.
Worker Thread 패턴으로 요구를 건넬 때에는 Producer-Consumer 패턴을 사용합니다.
ps. 쓰레드간에 직접 주고받는 통신을 하지 않고, 중간에 연결 고리를 두어서 그곳을 통해 거래를 하는 방식..
Balking 패턴.
문맥 : 복수의 쓰레드가 인스턴스를 공유하고 있다고 할때.
문제 : 복수의 쓰레드가 마음대로 인스턴스에 액세스하면 인스턴스의 안정성을 잃어버리게 된다.
그러나 안전한 타이밍을 기다리고 있으면 응답성이 저하된다.
해결법
인스턴스의 상태가 부적절한 때에는 처리를 중단합시다. 우선 인스턴스의 '적절한 상태'를 '가드 조건' 으로 표현한다. 그리고 안정성을 잃어버릴 위험이 있는 처리를 하기 전에 가드 조건이 충족되었는지 테스트 한다. 가드 조건이 충족되어 있을때에만 실행을 계속 한다.가드 조건이 충족되어 있지 않으면 실핼을 중단(balk)하고 곧장 돌아간다.
구현
자바에서는 가드 조건의 테스트에 if 문을 사용한다. balk하는 데에는 return으로 메소드에서 돌아오거나 throw로 예외를 던진다. 가드 조건의 테스트와 변경은 Single Threaded Execution 패턴을 사용한다.
관련
가드 조건이 충족되기까지 기다리고 싶을 때에는 Guarded Suspension을 사용합니다.
Balking 패턴의 가드 조건의 테스트와 변경을 기술하는 부분에서는 Single Threaded Execution 패턴을 사용합니다.
Guarded Suspension Patten
다른 용어 로는 Spin Lock, Guarded wait
복수의 쓰레드가 인스턴스를 공유하고 있고 마음대로 인스턴스에 액세스 하게 되면 인스턴스의 안정성을 잃어 버리게 된다. 이를 해결 하기 위해서 인스턴스의 상태가 부적절한때에는 적절한 상태가 되기까지 쓰레드를 기다리게 한다. 인스턴스의 '적절한 상태'를 '가드 조건'으로 표현하고 안정성을 잃어버릴 위험이 있는 처리를 하기 전에 가드 조건이 충족되어 있는지를 테스트 한다. 가드 조건이 충족되어 있지 않으면 만족될 때까지 쓰레드를 기다리게 한다.
ps. Guarded Suspension 패턴을 사용하면 가드 조건에 의해 메소드의 실행을 제어할 수가 있다. 하지만 가드 조건이 영원히 충족되지 않으면 쓰레드는 영원히 기다리게 되므로 생존성을 잃어 버리게 될 수 있다.
ps. 자바에서는 가드 조건 테스트에 while문을 사용해서 기다리기 위한 wait 메서드를 사용한다. 가드 조건이 변화 했을 경우 notify/notifyAll 메서드를 사용해서 쓰레드를 깨우면 된다.
ex.
import java.util.LinkedList;
public class RequestQueue {
private final LinkedList queue = new LinkedList();
public synchronized Request getRequest() {
while (queue.size() <= 0 ){
try {
wait();
} catch (InterruptedException e) {
// TODO: handle exception
}
}
return (Request)queue.removeFirst();
}
public synchronized void putRequest(Request request){
queue.addLast(request);
notifyAll();
}
}
Single Threaded Execution 패턴
이 다리를 건널 수 있는 사람은 오직 한 명
별명 : Critical Section, Critical Region
문맥 : 목수의 쓰레드가 인스턴스를 공유하고 있습니다.
문제 : 각각의 쓰레드가 인스턴스의 상태를 마음대로 변경하면 인스턴스의 안정성을 잃어버리게 됩니다.
해결법
우선 인스턴스가 불안정한 상태가 되는 범위(크리티컬 섹션)를 확실히 정해 둡니다. 그리고 크리티컬 섹션은 하나의 쓰레드만이 실행하도록 가드합니다. 그렇게 하면 인스턴스의 안정성은 지켜집니다.
구현
자바에서는 synchronized를 사용하여 크리티컬 섹션을 실현합니다.
관련
인스턴스의 상태가 변화하지 않을 때 스루풋을 향상시키기 위해서는 Immutable 패턴을 사용합니다.
인스턴스의 상태를 참조하는 쓰레드와 변경하는 쓰레드가 나누어져 있을 때에는 스루풋을 향상시키기 위해서 Read-Write Lock 패턴을 사용합니다.