ⓒ한빛미디어


해외에서 출간되어 번역된 애자일 관련 서적은 읽기가 힘들다. 책을 읽다 보면 손발이 오그라드는 적이 한 두번이 아니다. 기업 문화나 개발 문화의 차이라기 보다는 국가 간 문화 차이에서 비롯된 사고 방식과 행동 패턴이 다르기 때문이다. 민주주의를 수백 년간 체험하고 개인주의가 이미 일상이 되어 있는 사람들이 쓴 책에는 당연히 이러한 정서가 녹아 있다. 이를 우리 실정에 그대로 적용하기는 무리가 있다. 


다른 나라 개발자들에게 이런 내용을 자연스럽게 받아 들이는지 물어본 적이 없기 때문에 나만의 편견일 수도 있겠다. 하지만 이렇게 결정을 내릴 수 있는 근거은 평소 업무나 회의 때 보여주는 사람들의 모습이나 국내 ICT 일선에 있는 작가들이 지은 책들과 비교해 보면 분명 차이가 있기 때문이다.


그래도 나름 재미있게 읽었다. 소설이면서 그 구성이 독자의 판단에 따라 페이지를 왔다 갔다 하며 여러 가지 결론을 내릴 수 있는 방식이다. 어릴 적 추리 소설을 읽는 듯 반갑다. 이 점이 앞에서 이야기한 거부감을 낮춰 주었다. 표지는 만화로 되어 있어 싫어하는 분도 계시던데 오히려 이 점이 딱딱한 내용을 쉽고 재밌게 풀어주는 역할을 더했다고 본다.


내용은 꼬여 있는 프로젝트 팀에서 애자일 코치(주인공이자 독자)가 투입되면서 시작한다. 제품 기획자는 팀의 개발 속도를 고려하지 않고 일을 투입한다. 팀원들끼리는 사이가 대면대면하다. 애자일을 사내에서 최초로 도입해서 사용하고 있지만 불만이 많다. 이에 남은 3번의 스프린트 기간 동안 다른 애자일 팀과 협업해서 백로그를 팀의 사정에 맞게 재조정하고 경영진에게 보고하는 것으로 마무리 한다.


있을 법한 이야기를 잘 읽었다는 데에 만족하다. 실상은 이렇게 잘 풀리지 않는 경우가 더 많다. 능력있는 애자일 코치가, 아니 애자일 코치라는 인력 자체가 투입되는 경우가 드물다. 1주만에 팀웍이 눈에 띄게 좋아지지도 않는다. 결론은 애자일 도구와 실천법을 배우고 성공하는 팀의 분위기를 파악하는 데에 만족한다.


별점은 3개.

저작자 표시 비영리 변경 금지
신고

'Agile' 카테고리의 다른 글

드림팀의 악몽 애자일로 뒤엎기  (0) 2014.10.12
개인 업무 일일 회고  (0) 2014.10.12
굳어진 프로세스(시스템)  (0) 2012.01.05
애자일이 뭔가요?  (0) 2011.12.13
코드 리뷰에 관한 충고  (0) 2011.11.04
인지부조화와 스탠드업 미팅  (0) 2011.07.17

'드림팀의 악몽 애자일로 뒤엎기'라는 책을 읽다 문득 생각 나서 이 카테고리를 만들었다. 


애자일 코치인 주인공이 '코치 로그'라는 것을 남기기길래 유용하다 싶어 따라해 본다. 물론 이후의 글은 모두 비공개.


형식은 다음과 같다.

활동

  • ~~

  • ~~

잘한 일
  • ~~

  • ~~

잘못 한 일
  • ~~

  • ~~

궁금한 점
  • ~~

  • ~~

개선 활동
  • ~~

  • ~~

오늘의 점수: 10점만점에 8점



저작자 표시
신고

'Agile' 카테고리의 다른 글

드림팀의 악몽 애자일로 뒤엎기  (0) 2014.10.12
개인 업무 일일 회고  (0) 2014.10.12
굳어진 프로세스(시스템)  (0) 2012.01.05
애자일이 뭔가요?  (0) 2011.12.13
코드 리뷰에 관한 충고  (0) 2011.11.04
인지부조화와 스탠드업 미팅  (0) 2011.07.17


제목: (멀티 코어를 100% 활용하는) 자바 병렬 프로그래밍 / Java Concurrency in Practice

저자: 브라이어 게츠, 더그 리, 팀 피얼스, 조셉 보우비어, 데이빗 홈즈, 조슈아 블로쉬

출판사: 에이콘


하나의 스레드에 작업을 순차적으로 수행한다면?

==> 대부분의 작업은 I/O에 시간이 소요되므로, CPU는 놀게 된다.


작업마다 스레드를 직접 만들어 할당한다면? 

==> 스레드 생성/제거도 공짜가 아니다. 자원과 시간을 소모한다.

==> 생성할 수 있는 스레드 개수는 무한대가 아니다.


따라서 Executor 프레임웍을 사용하자!!


new Thread(runnable).start()

와 같은 코드가 보이면 Executor를 사용해서 유연한 실행 정책을 적용할 것을 고려해야 함.





ThreadPool을 이용할 때의 장점

- 매번 스레드를 새로 생성하지 않고 재활용한다. ==> 자원을 절약, 딜레이가 줄어들어 반응속도 향상. 

- 적절한 스레드 개수 조절로 CPU가 놀지 않도록 함


ThreadPool을 얻는 방법

- newFixedThreadPool

- newCachedThreadExecutor

- newSingleThreadExecutor

- newScheduledThreadPool




ExecutorService 인터페이스에는 동작주기를 관리할 수 있는 여러 메소드가 있다.

- void shutdown()

- List<Runnable> shutdownNow()

- boolean isShutdown()

- boolean isTerminated()

- boolean awaitTermination(long timeout, TimeUnit unit)




Timer 클래스 대신 ScheduledThreadPoolExecutor를 사용하자.

Timer의 단점: 

- 한개의 스레드만을 사용하기 때문에 주기적으로 실행되도록 했을 경우 만약 등록된 작업이 오래 걸린다면 작업이 완료된 후 밀려 있던 작업이 한꺼번에 수행되거나 정책에 따라 누락될 수도 있다.

- Exception 처리를 하지 않는다. Timer 스레드 자체가 멈춘 후 새로운 작업을 등록할 수도 없는 상태가 될 수 있다. 


DelayQueue 사용도 고려해 볼 것.




사용예: (브라우저에서) CompletionService를 이용한 웹페이지 렌더링



저작자 표시 비영리 변경 금지
신고


제목: (멀티 코어를 100% 활용하는) 자바 병렬 프로그래밍 / Java Concurrency in Practice

저자: 브라이어 게츠, 더그 리, 팀 피얼스, 조셉 보우비어, 데이빗 홈즈, 조슈아 블로쉬

출판사: 에이콘


동기화된 클래스의 대표주자: Vector, Hashtable


문제1. 이 역시 동시에 다른 스레드가 item을 추가/삭제할 경우 - ArrayIndexOutOfException 발생할 수 있음.

- 객체 자체에 synchronized 키워드를 사용하면 되지만 성능이 떨어지게 됨.


문제2. Iterator를 이용한 연산도중 동시성 이슈

- ConcurrentModificationException 발생: 즉시 멈춤(fail-fast)

- 숨겨진 iterator: toString, containAll, removeAll 등의 메소드 내에서 사용됨



병렬 컬렉션

Collections.synchronizedXXX 메소드는 동기화를 우선으로 구현됨. 성능저하.

병렬 컬렉션(java.util.concurrent 패키지에 포함)들은 동기화 보다는 성능을 우선시함. 따라서 연산 도중 size가 바뀔 수 있음. 


- ConcurrentHashMap (putIfAbsent와 같이 미리 구현되어 있는 연산 외 추가 연산이 필요할 경우는 ConcurrentMap을 사용하자)


- CopyOnWriteArrayList: 변경할 때마다 복사. Iterator는 뽑아낸 시점 기준으로 동작. 변경보다 읽기 작업이 많은 경우에 사용.



Blocking Queue (Producer-Consumer 패턴)

작업 생성/처리 부분을 완전히 분리할 수 있다.
- put: 큐가 가득차 있다면 추가할 공간이 생길 때까지 대기한다.
- take: 큐가 비어있는 상태라면 뽑아낼 값이 들어올 때까지 대기한다.
큐와 함께 쓰레드 풀을 사용하는 경우가 Producer-Consumer 패턴을 가장 흔한 경우이다.

- LinkedListBlockingQueue, ArrayBlockingQueue: FIFO 형태의 큐
- PriorityBlockingQueue: 우선순위 기준
- SynchronousBlockingQueue: 큐에 항목이 쌓이지 않는다. Producer는 Consumer에게 직접 작업을 전달해 준다. 따라서 누가 작업을 처리하는 지 알 수 있다. 데이터를 넘겨 받을 수 있는 충분한 개수의 Consumer가 대기하고 있을 경우에 사용하기 좋다.

사용예1. 데스크탑 검색: 파일 인덱싱 서비스
FileCrawler: 디스크에 들어 있는 디렉토리 계층 구조를 따라가면서 검색 댓아 파일이라고 판단되는 파일 이름을 작업 큐에 쌓는다.
Indexer: 작업 큐에 쌓여 있는 파일 이름을 뽑아내어 해당 파일의 내용을 색인한다.

사용예2. 직렬 쓰레드 한정(Serial Thread Confinement)
쓰레드에 한정된 객체는 특정 쓰레드 하나만이 소유권(객체의 내부에 대해 완벽히 알 수 있음)을 가질 수 있다. 
ex) 객체 풀: 객체 풀은 풀 내에 객체가 존재하지 않으면 객체의 상태에 대해 알 수 없다.

사용예3. Deque, 작업 가로채기(Work Stealing)
Producer-Consumer 패턴에서 모든 Consumer가 하나의 큐를 공유하는 것과는 달리 작업 가로채기 패턴은 모든 Consumer가 각자의 deque를 갖는다.
특정 Consumer가 자신의 작업을 모두 처리하면 다른 Consumer의 맨 뒤에 있는 작업을 가져와서 처리한다. 따라서 작업을 가져오기 위해 경쟁이 일어나지 않는다.
Consumer가 Producer의 역할도 갖고 있을 경우에 적용하기 좋다.
ex) Web crawler: 하나의 웹 페이지를 처리하고 나면 링크가 여러가 나타날 수 있다. 이를 deque에 쌓아 둔다.
ex) 가비지 컬렉터: 가비지 컬렉션 도중 힙을 마킹하는 작업


Blocking Method, Interruptable Method
스레드는 여러 가지 원인에 의해 블록 당하거나 멈춰질 수 있다.
- I/O 작업이 치고 들어와서 끝나기를 기다릴 때
- 락이 걸려있는 객체에 접근하려고 할 때
- Thread.sleep 메소드가 끝나기를 기다릴 때

블로킹 메소드: Thread.sleep과 같이 InterruptedException을 발생시키는 메소드. 이를 호출하는 메소드 역시 블로킹 메소드.

InterruptedException 발생시 대처방법
1. InterruptedException을 그대로 전달: 호출한 쪽에 떠넘기기. catch후 사용하던 리소스를 정리하고 나서 throw하는 경우도 있다.
2. 인터럽트를 무시하고 복구: Runnable을 구현한 경우가 해당됨. catch해서 현재 스레드의 interrupt() 메소드를 호출해서 인터럽트 상황을 알린다.




동기화 클래스(Synchronizer)

상태 정보를 사용해 스레드 간의 작업 흐름을 조절할 수 있도록 만들어진 클래스.

ex) Blocking Queue, Latch, FutureTask, Semaphore, Barrier


래치(Latch)

래치가 terminal 상태에 이를 때까지 모든 스레드의 동작을 중단시킨다.

  -. 특정자원을 확보하기 전에는 작업을 시작하지 말아야 할 때

  -. 의존성을 가지고 있는 다른 서비스가 시작하기 전에는 특정 서비스가 실행되지 않도록 막아야 하는 경우

  -. 특정 작업에 필요한 모든 객체가 실행할 준비를 갖출 때까지 기다리는 경우

한 번 terminal 상태에 다다르면 되돌릴 수 없다.


대표적인 래치: CountDownLatch

책의 예제: CountDownLatch를 이용하여 여러 개의 스레드가 동시에 작업을 시작하는 경우 작업을 완료하는 데에 걸리는 시간을 측정


FutureTask

- Executor 프레임웍에서 비동기적인 작업을 할 때,

- 시간이 많이 필요한 작업의 결과를 필요한 시점에 빨리 얻어내기 위해 작업을 미리 시켜 놓을 때 사용


연산작업은 Callable 인터페이스를 구현.

get 메소드는 작업이 종료되었다면 즉시 결과를 리턴, 아니면 종료 상태가 될 때까지 대기.

예외 처리에 주의!


세마포어(Semaphore)

특정 자원을 동시 사용하거나 특정연산을 동시에  호출하는 스레드의 수를 제한하고자 할 때 사용.

ex) 자원 pool이나 컬렉션의 크기에 제한을 두고자 할 때.


생성자에서 permit의 개수를 지정.

acquire 메소드로 permit을 요청하고 허용 가능한 permit이 없을 때는 대기.

release 메소드로 permit을 반납.


이진 세마포어(permit이 1인 경우)는 mutex로 사용 가능.


배리어(Barrier)

실제 작업은 여러 스레드에서 병렬로 처리하고, 다음 단계로 넘어가기 전에 이번 단계에서 계산해야 할 내용을 모두 취합해야 하는 등의 작업이 많이 일어나는 시뮬레이션 알고리즘에서 유용하게 사용 가능.


스레드는 배리어에 도달하면(자신의 작업을 마치면) await를 호출하여 다른 스레드가 배리어에 도달할 때까지 기다린 후 관문을 열고 다음으로 넘어간다.

(래치는 특정 '이벤트'가 일어날 때까지 기다렸다가 작업을 시작한다)

await 호출 후 타이아웃이 걸리거나 대기중 인터럽트가 걸리면 배리어는 깨진 것 으로 간주하고, 대기중인 모든 스레드에 BrokenBarrierException이 발생한다.


CyclicBarrier 클래스를 사용하여 배리어 포인트에서 반복적으로 만나는 기능을 모델링할 수 있다. 커다란 문제를 작은 문제로 분할하여 반복 처리하는 병렬 처리 알고리즘을 구현하고자 할 때 사용.


저작자 표시 비영리 변경 금지
신고


제목: (멀티 코어를 100% 활용하는) 자바 병렬 프로그래밍 / Java Concurrency in Practice

저자: 브라이어 게츠, 더그 리, 팀 피얼스, 조셉 보우비어, 데이빗 홈즈, 조슈아 블로쉬

출판사: 에이콘


스레드 안전한 객체를 만드는 방법.


4.2 인스턴스 한정

공유 데이터를 객체 내부에 숨겨두고, 이를 관리하는 부분에 락을 적용한다.

ArrayList나 Hashmap은 스레드 안전하지 않지만, Collections.synchronizedList와 같은 API로 생성한 컬렉션은 스레드 안전하다. 이는 래퍼 클래스를 거쳐야만 컬렉션 클래스의 내용을 사용할 수 있기 때문이다.


4.3 스레드 안정성 위임

스레드 안전성이 확보된 필드로만 구성된 클래스는 스레드에 안전할 수도 그렇지 않을 수도 있다.

여러 개의 필드가 서로 독립적(필드들이 서로의 상태 값에 대해 연관성이 없음)이 아닐 경우 문제가 된다.

클래스가 서로 의존성 없이 독립적이고 스레드 안전한 두 개 이상의 클래스(필드)를 조합해 만들어져 있고 두 개 이상의 클래스(필드)를 한번에 처리하는 복합 연산 메소드가 없는 상태라면, 스레드 안정성을 필드에 모두 위임할 수 있다.


4.4 스레드 안전하게 구현된 클래스에 기능 추가

스레드 안전한 클래스를 확장해서 사용할 경우 스레드 안정성을 확보하는 방법


4.4.1 호출하는 측의 동기화

아래 ListHelper클래스 처럼 list 객체에 락을 걸어야 쓰레드 안전하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public calss ListHelper<E> {
    public List<E> list = Collections.synchronizedList(new ArrayList<E>());
    ...
    public boolean putIfAbsent(E x) {
        synchronized(list) {
            boolean absent = !list.contains(x);
            if (absent) {
                list.add(x);
            }
            return absent;
        }
    }
}

putIfAbsent 메소드 자체에 synchronized 키워드만 붙인다고 해서 동기화된 list 객체에서 사용하는 lock과 동일한 lock을 사용하는 것이 아니기 때문이다.
자바 API 문서에 따르면 Collections.synchronizedList에서 반화하는 list객체를 이용하여 lock을 사용해야 한다고 명시되어 있다.

4.5 동기화 정책 문서화

적어도 @GuardedBy annotation (Java 1.5부터 지원)을 이용하여 어느 변수가 동기화되는지라도 표기해 두자.


저작자 표시 비영리 변경 금지
신고

티스토리 툴바