Chapter 13
동시성
스레드가 하나인 프로그램은 무엇과 언제가 서로 밀접
- 무엇(What)과 언제(When)을 분리하면, 애플리케이션 구조와 효율이 극적으로 나아짐
- ex) 서블릿 모델: EJB(Enterprize Java Bean) 컨테이너로 관리되는데, 이들은 동시성을 부분적으로 관리
- 실제로 컨테이너가 어떻게 동작하는지, 어떻게 동시 수정, 데드락 등과 같은 문제를 피하고 있는지 알아야 함
- 구조적 개선, 응답 시간과 작업 처리량 개선을 위해 동시성을 사용함
동시성 방어 원칙
- 단일 책임 원칙 (Single Responsibility Principle)
- 공유 객체를 사용하는 코드 내 critial section을
synchronized
키워드로 보호해야함
- 하지만 해당 키워드를 너무 많이 사용하게 된다면 모든 임계영역이 올바로 보호됐는지 확인하느라 리소스 소비됨.. (DRY 위반)
- DRY: Do not Repeat Yourself -> 반복하지 마라
- 객체를 복사해서 읽기 전용으로 사용
- 스레드는 가능한 독립적으로 구현: 각 스레드가 클라이언트 요청 하나를 처리하고, 모든 정보는 비공유 출처에서 가져오도록.
- 하지만 대다수 애플리케이션은 결국 데이터베이스 연결과 같은 자원을 공유하는 상황에 처하지..
- 스레드 환경에 안전한 컬렉션 사용하기
- ReentrantLock, Semaphore, CountDownLatch, ...
- 다중 스레드 애플리케이션 기본 term
- Bounded Resource: ex) 데이터베이스 연결, 길이 일정한 읽기/쓰기
- Mutual Exclusion: ex) 한 번에 한 스레드만 공유 자료/자원 사용 가능 (like DB write)
- Starvation: 스레드가 영원히/오랫동안 자원을 기다림
- DeadLock: 여러 스레드가 서로의 종료를 대기
- LiveLock: 락을 거는 단계에서 서로가 서로를 방해
- 생산자-소비자(Producer-Consumer)
- 생산자 -> 대기열 -> 소비자
- 대기열을 올바로 사용하고자 생산자/소비자 스레드는 서로에게 시그널
- 두 스레드 모두 진행 가능한데도 불구하고 서로에게서 시그널을 기다리는 상황 발생 가능
- 읽기-쓰기(Readers-Writers)
- 쓰기 스레드의 버퍼 점유로 여러 읽기 쓰레드가 버퍼를 대기하다가 throuput 저하
- 식사하는 철학자들(Dining Philosoper)
공유 객체 하나에는 메서드 하나만 사용하고 synchronized
키워드를 사용하는게 권장되지만, 그렇지 않은 경우 아래와 같이 해결한다.
- 클라단: 클라이언트에서 첫번째 메소드 호출 전에 서버를 잠그고, 마지막 메서드 호출시까지 잠금 유지
- 서버단: 서버에다가 서버를 잠그고 모든 메서드를 호출한 후 잠금을 해제하는 메서드를 구현
synchronized
키워드의 남발은 결국 많은 락을 불러일으키고, 이는 스레드 지연 및 부하의 가중으로 이어지므로 critical section을 최대한 줄이자.
- DeadLock
- ex) Producer-Consumer 모델에서 Producer 스레드는 재빨리 종료했는데 Consumer 스레드가 Producer의 메시지를 영원히 기다리는 상황이 존재할 수 있음.
스레드 코드 테스트
- 프로세서 수보다 많은 스레드를 돌려보자
- 다중 스레드를 쓰는 코드 부분을 다양한 환경에 쉽게 끼워 넣을 수 있게 스레드 코드를 구현
- 코드에 보조 코드를 넣어서 해당 코드에 접근할 수 있는 다양한 경로를 제안하여 실패를 일으키게 해본다.
- ex) yield(), sleep(), ... 즉 jiggle 시킴
결론
- 여러 스레드가 공유 자료를 조작하거나 자원 풀 공유 시 동시성 오류가 발생
- 방지를 위해서 잠긴 영역에서 다른 잠긴 영역을 호출하지 않도록 한다
- 스레드 코드는 많은 플랫폼에서 많은 설정으로 반복해서 계속 테스트 해야함