Skip to content

Commit 3da0513

Browse files
committed
13. 동시성
1 parent a52e7b5 commit 3da0513

File tree

1 file changed

+154
-0
lines changed

1 file changed

+154
-0
lines changed

13. 동시성/Readme.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# 동시성
2+
##### 객체는 처리의 추상화. 스레드는 일정의 추상화
3+
4+
## 동시성이 필요한 이유?
5+
#### 동시성은 결합을 없애는 전략. 즉, what와 when을 분리하는 전략
6+
### 미신과 오해
7+
- 동시성은 항상 성능을 높여준다?
8+
> 동시성은 '때로' 성능을 높여준다. 대기시간이 아주 길어 여러 스레드가 프로세서를 공유할 수 있거나, 여러 프로세서가 동시에 처리할 독립적인 계산이 충분히 많은 경우에만 성능이 높아진다.
9+
- 동시성을 구현해도 설계는 변하지 않는다?
10+
> 크게 달라진다.
11+
- 웹 또는 EJB컨테이너를 사용하면 동시성을 이해할 필요가 없다?
12+
> 알아야한다.
13+
14+
### 타당한 생각 몇가지
15+
- 동시성은 다소 부하를 유발한다.
16+
- 동시성은 복잡하다
17+
- 일반적으로 동시성 버그는 재현하기 어렵다.
18+
- 동시성을 구현하려면 흔히 근본적인 설계 전략을 재고해야 한다.
19+
20+
## 난관
21+
```
22+
public class X {
23+
private int lastIdUsed;
24+
25+
public int getNextId() {
26+
return ++lastIdUsed;
27+
}
28+
}
29+
```
30+
> 인스턴스 X를 생성, lastIdUsed = 42; 두 스레드가 해당 인스턴스를 공유, 두 스레드가 getNextId(); 호출
31+
결과는? ->
32+
- case1) A 스레드는 43을 받는다. B 스레드는 44를 받는다. lastIdUsed는 44가 된다.
33+
- case2) A 스레드는 44를 받는다. B 스레드는 43을 받는다. lastIdUsed는 44가 된다.
34+
- case3) 한 스레드는 43을 받는다. 다른스레드는 43을 받는다. lastIdUsed는 43이 된다.
35+
#### -> 일부 잘못된 결과를 내놓는다. 경로의 갯수는? JIT(Just-In-TIme) 컴파일러가 바이트 코드를 처리하는 방식과 자바 메모리 모델이 원자로 간주하는 최소단위를 알아야함. 간략하게는 잠재적인 경로는 최대 12,870개. 데이터 타입을 int에서 long으로 바꾼다면? 경로는 2,704,156개
36+
37+
## 동시성 방어 원칙
38+
### 단일 책임 원칙 SRP ( Single Responsibility Principle )
39+
> SRP? ( "주어진 메서드/클래스/컴포넌트를 변경할 이유가 하나여야한다" 는 원칙)
40+
동시성은 복잡성 하나만으로도 따로 분리할 이유가 충분함.
41+
동시성을 구현할 때는 다름 몇 가지를 고려하자
42+
- 동시성 코드는 독자적인 개발, 변경, 조율 주기가 있다.
43+
- 동시성 코드에는 독자적인 난관이 있다. 다른 코드에서 겪는 난관과 다르며 훨씬 어렵다.
44+
- 잘못 구현한 동시성 코드는 별의별 방식으로 실패한다. 주변에 있는 다른 코드가 발목을 잡지 않더라도 동시성 하나만으로도 충분히 어렵다.
45+
> 권장사항: 동시성 코드는 다른 코드와 분리하라.
46+
47+
### 따름 정리(corollary): 자료 범위를 제한하라.
48+
> 앞에서의 예제와 같은일이 벌어지지 않게 하기위해서는 임계영역(critical section)을 만들어야함. 공유 자료를 수정하는 위치가 많을수록 다음 가능성도 커진다.
49+
- 보호할 임계영역을 빼먹는다. 그래서 공유 자료를 수정하는 모든 코드를 망가뜨린다.
50+
- 모든 임계영역을 올바로 보호했는지(DRY 위반) 확인하느라 똑같은 노력과 수고를 반복한다.
51+
- 그렇지 않아도 찾아내기 어려운 버그가 더욱 찾기 어려워진다.
52+
53+
### 따름정리: 자료 사본을 사용하라
54+
> 공유 자료를 줄이려변 처음부터 공유하지 않는 방법이 가장 좋다. 예를들면 데이터를 복사해서 쓴다던가.. 만약 데이터를 복사함으로 생기는 리소스가 걱정된다면? 정말 복사함으로써 잃는 리소스와 공유자료를 사용하면서 생길 수 있는 리스크중 리소스가 더 아까운지 한번 더 생각을 해보자.
55+
56+
### 따름정리: 스레드는 가능한 독립적으로 구현하라.
57+
> 자신만의 세상에 존재하는 스레드를 구현한다. 즉, 다른 스레드와 자료를 공유하지 않는다.
58+
> 권장사항: 독자적인 스레드로, 가능하면 다른 프로세서에서, 돌려도 괜찮도록 자료를 독립적인 단위로 분할하라.
59+
60+
61+
## 라이브러리를 이해하라
62+
##### 자바 5로 스레드 코드를 구현한다면 다음을 고려하라
63+
- 스레드 환경에 안전한 컬렉션을 사용한다. (자바 5부터 제공)
64+
- 서로 무관한 작업을 수행할 때는 executor 프레임워크를 사용한다.
65+
- 가능하다면 스레드가 차단(blocking)되지 않는 방법을 사용한다.
66+
- 일부 클래스 라이브러리는 스레드에 안전하지 못하다.
67+
68+
### 스레드 환경에서 안전한 컬렉션
69+
-> 현기에 알아서 찾아라~! 여기에 기입해놓고 나중에 읽을것같으면 수정하삼
70+
71+
## 실행 모델을 이해하라
72+
##### 다중 스레드 어플리케이션을 분류하는 방식은 여러 가지이다.
73+
- 한정된 자원(Bound Resource): 다중 스레드 환경에서 사용하는 자원으로, 크기나 숫자가 제한적이다. 데이터베이스 연결, 길이가 일정한 읽기/쓰기 버퍼등이 대표적
74+
- 상호 배제(Mutual Exclusion): 한 번에 한 스레드만 공유 자료나 공유 자원을 사용할 수 있는 경우를 가리킨다.
75+
- 기아(Stavation): 한 스레드나 여러 스레드가 괸장히 오랫동안 혹은 영원히 자원을 기다린다. 예를들어, 항상 짧은 스레드에게 우선순위를 준다면, 짧은 스레드가 지속적으로 이어질 경우, 긴 스레드가 기아상태에 빠진다.
76+
- 데드락(Deadlock): 여러 스레드가 서로가 끝나기를 기다린다. 모든 스레드가 각기 필요한 자원을 다른 스레드가 점유하는 바람에 어느 쪽도 더 이상 진핸하지 못한다.
77+
- 라이브락(Livelock): 락을 거는 단계에서 각 스레드가 서로를 방해한다. 스레드는 계속해서 진행하려 하지만, 공명(resonance)으로 인해, 굉장히 오랫동안 혹은 영원히 진행하지 못한다.
78+
79+
### 생상자-소비자(Producer-Consumer)
80+
> 생산자가 정보를 생산하면 버퍼(buffer)나, 대기열(queue)에 넣는다. 그러면 소비자가 버퍼or 대기열(이하 버퍼)을 확인하고 정보가 있으면 사용한다.
81+
> 생산자는 버퍼에 빈공간이 있어야 정보를 생산, 주입(?)하고 소비자는 버퍼에 정보가 있어야 해당 버퍼에 접근을 해 정보를 사용한다.
82+
> 생산자는 정보를 생성하면 소비자에게 버퍼에 정보가 있다는것을 알리고, 소비자는 정보를 가져와 버퍼에 공간이 있다면 생산자에게 빈공간이 있다는것을 알린다(시그널).
83+
> 따라서 잘못하면 생산자 스레드와 소비자 스레드가 둘 다 진행 가능함에도 불구하고 동시에 서로에게서 시그널을 기다릴 가능성이 존재한다.
84+
85+
### 읽기-쓰기(Readers-Wirters)
86+
> 쓰기 스레드가 공유자원을 갱신하는 경우 -> 처리율(throughput)이 문제의 핵심, 처리율을 강조하면 기아(starvation)현상이 생기거나 오래된 정보가 쌓임
87+
> 쓰기 스레드가 자료를 갱신할때 읽기스레드가 접근하지 못하게 하거나, 읽기스레드가 읽을때 쓰기스레드가 접근하지 못하게 한다면 복잡한 균형잡기가 필요함. 처리율이 떨어짐.
88+
89+
### 식사하는 철학자들(Dining Philosophers)
90+
> 원탁에 철학자들이 앉아있음. 철학자들 왼쪽에는 포크가 있고, 원탁 중앙에는 스파게티가 있음.철학자들은 배가고프지 않다면 생각을하고, 배가고프면 양손에 포크를 들고 스파게티를 먹는다. 양손에 포크가 없다면 스파게티를 먹을 수 없다.
91+
> 자신의 왼쪽이나 오른쪽 철학자가 포크를 들고 스파게티를 먹을때는 양손에 포크를 들 수 없기때문에 배고파도 포크를 기다려야한다.
92+
> 여기서 철학자를 스레드로, 포크를 자원으로 바꿔서 생각해보자.
93+
94+
### 권장사항: 위에서 설명한 기본 알고리즘과 각 해법을 이해하라.
95+
96+
## 동기화하는 메서드 사이에 존재하는 의존성을 이해하라
97+
동기화하는 메서드 사이에 의존성이 존재하면 동시성 코드에 찾아내기 어려운 버그가 생긴다.
98+
> 권장사항: 공유 객체 하나에는 메서드 하나만 사용하라.
99+
공유 객체 하나에 여러 메서드가 필요한 상황도 생긴다. 그럴 때는 다음 세 가지 방법을 고려한다.
100+
- 클라이언트에서 잠금 - 클라이언트에서 첫 번째 메서드를 호출하기 전에 서버를 잠근다. 마지막 메서드를 호출할 때까지 잠금을 유지한다.
101+
- 서버에서 잠금 - 서버에다 "서버를 잠그고 모든 메서드를 호출한 후 잠금을 해제하는" 메서드를 구현한다. 클라이언트는 이 메서드를 호출한다.
102+
- 연결(Adapted 서버) - 잠금을 수행하는 중간 단계를 생성한다. '서버에서 잠금' 방식과 유사하지만 원래 서버는 변경하지 않는다.
103+
104+
## 동기화 하는 부분을 작게 만들어라
105+
자바에서 synchronized키워드를 사용하면 락을 설정한다. 같은 락으로 감싼 모든 코드 영역은 한 번에 한 스레드만 실행이 가능하다. 락은 스레드를 지연시키고 부하를 가중시킨다. -> synchronized를 남발하지 말자. 하지만 임계영역은 반드시 보호해야함으로, 코드를 짤 때 임계영역 수를 최대한 줄여야 한다.
106+
> 권장사항: 동기화하는 부분을 최대한 작게 만들어라.
107+
108+
## 올바른 종료 코드는 구현하기 어렵다
109+
깔끔하게 종료하는 코드는 올바로 구현하기 어렵다. 가장 흔한것은 데드락. 깔끔하게 종료하는 다중 스레드 코드를 짜야한다면 시간을 투자해 올바로 구현하기를 바란다.
110+
> 권장사항: 종료 코드를 개발 초기부터 고민하고 동작하게 초기부터 구현하라. 생각보다 오래 걸린다. 생각보다 어려우므로 이미 나온 알고리즘을 검토하라.
111+
112+
## 스레드 코드 테스트하기
113+
코드가 올바르다고 증명하기는 현실적으로 불가능하다. 그럼에도 충분한 테스트는 위험을 낮춘다.
114+
> 권장사항: 문제를 노출하는 테스트 케이스를 작성하라. 프로그램 설정과 시스템 설정과 부하를 바꿔가며 자주 돌려라. 테스트가 실패하면 원인을 추적하라. 다시 돌렸더니 통과하더라는 이유로 그냥 넘어가면 절대로 안 된다.
115+
116+
- 말이 안 되는 실패는 잠정적인 스레드 문제로 취급하라.
117+
> 재현하기 매우 어렵다. 하지만 일회성 문제를 계속 무시한다면 잘못된 코드위에 코드가 계속 쌓인다.
118+
> 권장사항: 시스템 실패를 일회성이라 치부하지 마라.
119+
120+
- 다중 스레드를 고려하지 않은 순차 코드부터 제대로 돌게 만들자.
121+
> 스레드 환경 밖에서 코드가 제대로 도는지 반드시 확인한다. 일반적인 방법으로, 스레드가 호출하는 POJO를 만듬.
122+
> 권장사항: 스레드 환경 밖에서 생기는 버그와 스레드 환경에서 생기는 버그를 동시에 디버깅하지 마라. 먼저 스레드 환경 밖에서 코드를 올바로 돌려라.
123+
124+
- 다중 스레드를 쓰는 코드 부분을 다양한 환경에 쉽게 끼워 넣을 수 있게 스레드 코드를 구현하라
125+
> 다중 스레드를 쓰는 코드를 다양한 설정으로 실행하기 쉽게 구현하라
126+
> 한 스레드로 실행하거나, 여러 스레드로 실행하거나, 실행 중 스레드 수를 바꿔본다.
127+
> 스레드 코드를 실제 환경이나 테스트 환경에서 돌려본다.
128+
> 테스트 코드를 빨리, 천천히, 다양한 속도로 돌려본다.
129+
> 반복 테스트가 가능하도록 테스트 케이스를 작성한다.
130+
> 권장사항: 다양한 설정에서 실행할 목적으로 다른 환경에 쉽게 끼워넣을 수 있게 코드를 작성하라.
131+
132+
- 다중 스레드를 쓰는 코드 부분을 상황에 맞춰 조정할 수 있게 작성하라.
133+
134+
- 프로세서 수보다 많은 스레드를 돌려보라.
135+
> 스와핑(swapping)할 때도 문제가 발생한다. 스와핑이 잦을 수록 임계영역을 빼먹은 코드나 데드락을 일으키는 코드를 찾기 쉬워진다.
136+
137+
- 다른 플랫폼에서 돌려보라.
138+
139+
- 코드에 보조 코드를 넣어 볼려라. 강제로 실패를 일으키게 해보라.
140+
> 보조코드를 추가해 코드가 실행되는 순서를 바꿔준다 ex) Object.wait(), Object.sleep() ~
141+
> 코드에 보조코드를 추가하는 방법은 두가지. 1) 직접 구현하기 2) 자동화
142+
> 보조코드를 직접 구현하기는 문제가 많으니 자동화를 사용하는걸 추천. (AOF, CGLIB, ASM~~)
143+
> 권장사항: 흔들기 기법을 사용해 오류를 찾아내라.
144+
145+
146+
147+
148+
149+
150+
151+
152+
153+
154+

0 commit comments

Comments
 (0)