Clean code appendix 1

508 views
421 views

Published on

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
508
On SlideShare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
9
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Clean code appendix 1

  1. 1. Appendix-A 동시성 II chois7912년 9월 10일 월요일
  2. 2. Review of Chapter-13 동시성 I • 다중 스레드(동시성) 코드는 올바로 구현하기가 어렵다 • 동시성의 오해 • 동시성은 항상 성능을 높여준다 • 동시성을 구현해도 설계는 변하지 않는다 • 동기화 라이브러리를 사용하면 동시성을 이해할 필요가 없다 • 동시성 방어 원칙 • SRP(단일 책임 원칙): 동시성은 복잡성 하나만으로도 따로 분리할 이유가 충분 • 공유 데이터의 최소화 및 캡슐화 • 복사본을 사용 • 스레드는 가능한한 독립적으로 구현 • 동시성 구현 코드 테스트 • 완벽한 증명하기는 현실적으로 불가능 • 많은 플랫폼에서 많은 구성으로 반복해서 테스트가 필요12년 9월 10일 월요일
  3. 3. 클라이언트/서버 예제(1/4) • Non-Thread Version ServerSocket serverSocket = new ServerSocket(8009); while (keepProcessing) { try { Socket socket = serverSocket.accept(); process(socket); } catch (Exception e) { handle(e); } } • 만약 성능 이슈가 발생한다면? • 응용 프로그램의 수행시간 종류 • 프로세서 - 수치 계산, 정규 표현식 처리, 가비지 컬렉션 • I/O - 소켓 사용, 데이터 베이스 연결, 가상 메모리 스와핑 기다리기 • 멀티 스레드로 성능 개선 가능12년 9월 10일 월요일
  4. 4. 클라이언트/서버 예제(2/4) • Thread Version void process(final Socket socket) { if (socket == null) return; Runnable clientHandler = new Runnable() { public void run() { try { String message = MessageUtils.getMessage(socket); MessageUtils.sendMessage(socket, "Processed: " + message); closeIgnoringException(socket); } catch (Exception e) { e.printStackTrace(); } } }; Thread clientConnection = new Thread(clientHandler); clientConnection.start(); } • 이 코드의 문제점 • 하나 이상의 책임: 소켓 연결 관리, 클라이언트 처리, 스레드 정책, 서버 종료 정책 • 다양한 추상화 수준12년 9월 10일 월요일
  5. 5. 클라이언트/서버 예제(3/4) • 책임별로 클래스를 분리 • ClientConnection: 소켓 연결 관리 • ClientRequestProcessor: 클라이언트 처리 • ClientScheduler: 스레드 정책 • ConnectionManager: 서버 종료 정책 public void run() { while (keepProcessing) { try { ClientConnection clientConnection = connectionManager.awaitClient(); ClientRequestProcessor requestProcessor = new ClientRequestProcessor(clientConnection); clientScheduler.schedule(requestProcessor); } catch (Exception e) { e.printStackTrace(); } } connectionManager.shutdown(); }12년 9월 10일 월요일
  6. 6. 클라이언트/서버 예제(4/4) • 스레드 정책 코드 및 확장 public interface ClientScheduler { public void schedule(ClientRequestProcessor requestProcessor); } public class ThreadPerRequestScheduler implements ClientScheduler { @Override public void schedule(final ClientRequestProcessor requestProcessor) { Runnable runnable = new Runnable() { public void run() { requestProcessor.process(); } }; Thread thread = new Thread(runnable); thread.start(); } } // Java Executor framework을 사용한 Thread 정책 변경 public class ExecutorClientScheduler implements ClientScheduler { Executor executor; public ExecutorClientScheduler(int availableThreads) { executor = Executors.newFixedThreadPool(availableThreads); } public void schedule(final ClientRequestProcessor requestProcessor) { Runnable runnable = new Runnable() { public void run() { requestProcessor.process(); } }; executor.execute(runnable); } }12년 9월 10일 월요일
  7. 7. 가능한 실행 경로(1/2) • 2개의 스레드로 이 코드를 실행한다면? public class IdGenerator { int lastIdUsed; public int incrementValue() { return ++lastIdUsed; } } • lastIdUsed의 초기값이 93일 경우 • 스레드 1이 94를 얻고, 스레드 2가 95를 얻고, lastIdUsed가 95가 된다 • 스레드 1이 95를 얻고, 스레드 2가 94를 얻고, lastIdUsed가 95가 된다 • 스레드 1이 94를 얻고, 스레드 2가 94를 얻고, lastIdUsed가 94가 된다 • JVM의 구현 방식에 따라 이 결과도 가능12년 9월 10일 월요일
  8. 8. 가능한 실행 경로(2/2) • 경로의 수 • return ++lastIdUsed는 바이트 코드 8개로 구성됨 • 루프나 분기가 없이 명령 N개를 T개의 스레드로 실행했을때 가능한 경로의 수 • Ex) N = 8이고, T = 2이면 가능한 경로의 수: 12,870 • Synchronized를 적용했을 경우 • 가능한 경로의 수: 2(T) public synchronized int incrementValue() { return ++lastIdUsed; }12년 9월 10일 월요일
  9. 9. 라이브러리를 이해하라(1/3) • Executor 프레임워크 • 스레드 풀 관리 • Runnable 인터페이스 지원 • Callable/Future 인터페이스 지원 • 스레드의 수행 결과를 받아오기 위해 사용 public String processRequest(String message) throws Exception { Callable<String> makeExternalCall = new Callable<String>() { public String call() throws Exception { String result = ""; // make external request return result; } }; Future<String> result = executorService.submit(makeExternalCall); String partialResult = doSomeLocalProcessing(); return result.get() + partialResult; }12년 9월 10일 월요일
  10. 10. 라이브러리를 이해하라(2/3) • 스레드를 중단하지 않는 방법 • Synchronized 및 Lock • 비관적 잠금: 항상 락을 사용 public class ObjectWithValue { private int value; public synchronized void incrementValue() { ++value;} public int getValue() { return value; } } • AtomicInteger 사용 - Concurrent package • 낙관적 잠금: 현재 변수 값이 최종으로 알려진 값일 경우 갱신하고, 그렇지 않을 경우 성공할때까지 재 시도 • 프로세서의 CAS(Compare And Swap) 연산을 사용 public class ObjectWithValue { private AtomicInteger value = new AtomicInteger(); public void incrementValue() { value.incrementAndGet(); } public int getValue() { return value.get(); } // CAS의 구현 int variableBeingSet; void simulateNonBlockingSet(int newValue) { int currentValue; do { currentValue = variableBeingSet; } while (currentValue != compareAndSwap(currentValue, newValue)); }12년 9월 10일 월요일
  11. 11. 라이브러리를 이해하라(3/3) • 스레드에 안전하지 않은 클래스 • SimpleDateFormat, java.util 컨테이너 클래스... • 해결 방안 • 스레드 안전 라이브러리 사용 • 직접 구현 • 클라이언트 기반 잠금 메커니즘 • 공유 데이터를 사용하는 모든 곳에서 lock을 사용 • 서버 기반 잠금 메커니즘 • 공유 데이터에 접근을 제어하는 별도의 클래스로 랩핑12년 9월 10일 월요일
  12. 12. 메소드 사이에 존재하는 의존성을 조심하라(1/3) • 의존성 예제 • 여러 메소드에서 하나의 변수를 공유해서 사용 public class IntegerIterator implements Iterator<Integer> { private Integer nextValue = 0; public synchronized boolean hasNext() { return nextValue < 100000; } public synchronized Integer next() { if (nextValue == 100000) throw new IteratorPastEndException(); return nextValue++; } public synchronized Integer getNextValue() { return nextValue; } } • 만약 다중 스레드가 IntegerIterator 인스턴스 하나를 공유 한다면? while(iterator.hasNext()) { int nextValue = iterator.next(); // nextValue로 뭔가를 한 } 오류 발생: nextValue가 100000을 넘을 가능성이 존재12년 9월 10일 월요일
  13. 13. 메소드 사이에 존재하는 의존성을 조심하라(2/3) • 해결책 • 실패를 용인 • 클라이언트 기반 잠금: 모든 클라이언트에서 동기화 로직 구현 • DRY(Don’t Repeat Yourself) 원칙 위반: 중복으로 인한 오류 발생이 쉬움 while (true) { int nextValue; Client-based synchronized (iterator) { if (!iterator.hasNext()) break; nextValue = iterator.next(); } doSometingWith(nextValue); Server-based } public class IntegerIteratorServerLocked { private Integer nextValue = 0; public synchronized Integer getNextOrNull() { • 서버 기반 잠금 if (nextValue < 100000) return nextValue++; • 코드 중복이 줄어듬: 오류가 발생할 가능성이 줄어듬 else return null; } • 성능이 좋아짐 } while (true) { • 스레드 정책이 하나임 int nextValue = iterator.getNextOrNull();; if (next == null) • 공유 변수가 줄어든다 break; doSometingWith(nextValue); }12년 9월 10일 월요일
  14. 14. 메소드 사이에 존재하는 의존성을 조심하라(3/3) • 서버 코드에 손대지 못하는 경우 • Adapter 패턴을 적용 public class ThreadSafeIntegerIterator { private IntegerIterator iterator = new IntegerIterator(); public synchronized Integer getNextOrNull() { if (iterator.hasNext()) return iterator.next(); return null; } }12년 9월 10일 월요일
  15. 15. 작업 처리량 높이기 • EX) 네트워크에서 페이지를 읽어 분석하는 프로그램 • 가정 • 페이지를 읽어 오는 평균 I/O 시간: 1초 • 페이지를 분석하는 평균 처리시간: 0.5초 • 처리는 CPU 100% 사용, I/O는 CPU 0% 사용 • 단일 스레드 환경 • N 페이지를 처리하는 총 실행시간: 1.5초 * N • 멀티 스레드 환경 • 3개의 스레드로 동시에 실행한다면? • I/O 1초동안 다른 2개의 스레드는 페이지를 분석할 수 있어 단일 스레드에 비해 약 3배의 처리 속도를 가짐12년 9월 10일 월요일
  16. 16. 데드락(1/2) • 4가지 조건을 모두 만족하는 경우 데드락 발생 • 상호배제(Mutual exclusion) use Thread#1 Res1 Thread#2 not use • 잠금&대기(Lock&Wait) use Thread#1 Res1 Thread#2 wait • 선점불가(No Preemption) use Thread#1 Res1 Thread#2 not preemptive • 순환대기(Circular Wait) lock Res1 request Thread#2 Thread#1 request Res2 lock12년 9월 10일 월요일
  17. 17. 데드락(2/2) • 해결 방법 • 상호배제(Mutual exclusion) • 스레드 수 이상으로 자원을 늘려서 해결 가능: 일반적으로 힘듬 • 잠금&대기(Lock&Wait) • 필요한 모든 자원을 점유하지 못한다면 모든 자원을 반환하여 해결 가능 • 기아(Starvation), 라이브락(Livelock) 등의 문제가 발생할 수 있음 • 선점불가(No Preemption) • 자원을 소유중인 스레드에게 해제를 요청하는 메커니즘을 사용하여 해결 가능 • 이러한 구현 및 관리가 쉽지 않음 • 순환대기(Circular Wait) • 데드락을 방지하는 가장 흔한 전략중 하나 • 모든 자원을 똑같은 순서대로 할당하게 만들어 해결 가능 • 자원 할당 순서가 변경 불가능할 수 있음12년 9월 10일 월요일
  18. 18. 다중 스레드 코드 테스트 • 다중 스레드를 테스트 하여 검증하기는 쉽지 않다 • 권장 방법 • 몬테 카를로 테스트 • 조율이 가능하게 유연한 테스트를 만들고, 임의의 값을 조율하며 반복하며 테스트 • 시스템을 배치할 모든 플랫폼에서 테스트 • 부하가 변하는 장비에서 테스트12년 9월 10일 월요일

×