Skip to content

Commit 8800617

Browse files
authored
📝 docs: 클린코드 13장 동시성 (작성중)
작성중인 챕터
1 parent 013216a commit 8800617

File tree

1 file changed

+126
-0
lines changed

1 file changed

+126
-0
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
---
2+
title: "[Book - Clean Code] 13. 동시성 (작성중)"
3+
date: 2025-10-24 18:00:00 +0900
4+
categories: [Book - Clean Code]
5+
tags: [book, clean code]
6+
---
7+
8+
동시성과 깔끔한 코드는 양립하기 어렵다.
9+
10+
## 동시성이 필요한 이유?
11+
동시성은 coupling을 없애는 전략이다.
12+
즉, **무엇****언제**를 분리하는 전략이다.
13+
14+
스레드가 하나인 프로그램은 무엇과 언제가 서로 밀접하여 호출 스택을 살펴보면 프로그램 상태가 곧바로 드러난다.
15+
16+
무엇과 언제를 분리하면 애플리케이션 구조와 효율이 극적으로 나아진다.
17+
18+
단일 스레드 수집기는 웹 소켓에서 입출력을 기다리는 시간이 아주 많다.
19+
한 번에 한 사이트를 방문하는 대신 다중 스레드 알고리즘을 이용하면 수집기 성능을 높일 수 있다.
20+
21+
### 미신과 오해
22+
* 동시성에 대한 미신과 오해
23+
* 동시성은 항상 성능을 높여준다.
24+
* 동시성을 구현해도 설계는 변하지 않는다.
25+
* 웹 또는 EJB 컨테이너를 사용하면 동시성을 이해할 필요가 없다.
26+
* 동시성에 대한 타당한 생각
27+
* 동시성은 다소 부하를 유발한다.
28+
* 동시성은 복잡하다.
29+
* 일반적으로 동시성 버그는 재현하기 어렵다.
30+
* 동시성을 구현하려면 흔히 근본적인 설계 전략을 재고해야 한다.
31+
32+
## 동시성 방어 원칙
33+
34+
### 단일 책임 원칙(SRP)
35+
SRP는 주어진 메서드/클래스/컴포넌트를 변경할 이유가 하나여야 한다는 원칙이다.
36+
37+
동시성은 복잡성 하나만으로도 따로 분리할 이유가 충분하다.
38+
따라서 동시성 관련 코드는 다른 코드와 분리해야 한다.
39+
40+
동시성을 구현할 때는 아래 항목을 고려한다.
41+
* 동시성 코드는 독자적인 개발, 변경, 조율 주기가 있다.
42+
* 동시성 코드에는 독자적인 난관이 있다.
43+
* 다른 코드에서 겪는 난관과 다르며, 훨씬 어렵다.
44+
* 잘못 구현한 동시성 코드는 별의별 방식으로 실패한다.
45+
* 주변에 있는 다른 코드가 발목을 잡지 않더라도 동시성 하나만으로도 충분히 어렵다.
46+
47+
> 권장사항: 동시성 코드는 다른 코드와 분리하라
48+
49+
### 따름 정리: 자료 범위를 제한하라
50+
객체 하나를 공유한 후 동일 필드를 수정하던 두 스레드가 서로 간섭하므로 예상치 못한 결과를 내놓는다.
51+
이런 문제를 해결하는 방안으로 공유 객체를 사용하는 코드 내 임계영역을 synchronized 키워드로 보호하라고 권장한다.
52+
53+
> 권장사항: 자료를 캠슐화하라, 공유 자료를 최대한 줄여라.
54+
55+
### 따름 정리: 자료 사본을 사용하라
56+
공유 자료를 줄이려면 처음부터 공유하지 않는 방법이 제일 좋다.
57+
어떤 경우에는 객체를 복사해 읽기 전용으로 사용하는 방법이 가능하다.
58+
59+
하지만 사본으로 동기화를 피할 수 있다면 내부 잠금을 없애 절약한 수행 시간이 사본 생성과 GC에 드는 부하를 상쇄할 가능성이 크다.
60+
61+
### 따름 정리: 스레드는 가능한 독립적으로 구현하라
62+
다른 스레드와 자료를 공유하지 않게 한다.
63+
64+
> 권장사항: 독자적인 스레드로, 가능하면 다른 프로세서에서, 돌려도 괜찮도록 자료를 독립적인 단위로 분할하라.
65+
66+
## 라이브러리를 이해하라
67+
68+
### 스레드 환경에 안전한 컬렉션
69+
> 권장사항: 언어가 제공하는 클래스를 검토하라.
70+
>
71+
> `java.util.concurent`, `java.util.concurrent.atomic`, `java.util.concurrent.locks`
72+
73+
## 실행 모델을 이해하라
74+
다중 스레드 애플리케이션을 분류하는 방식은 여러 가지이므로, 실행 모델을 몇 가지 살펴보자.
75+
76+
### 생산자-소비자 (Producer-Consumer)
77+
하나 이상 생산자 스레드가 정보를 생성해 buffer나 대기열(queue)에 넣는다.
78+
하나 이상 소비자 스레드가 대기열에서 정보를 가져와 사용한다.
79+
80+
생산자 스레드와 소비자 스레드가 사용하는 대기열은 **한정된 자원**이다.
81+
82+
생산자 스레드는 대기열에 빈 공간이 있어야 정보를 채운다.
83+
즉, 빈 공간이 생길 때까지 기다린다.
84+
85+
소비자 스레드는 대기열에 정보가 있어야 가져온다.
86+
즉, 정보가 채워질 때까지 기다린다.
87+
88+
생산자 스레드는 대기열에 정보를 채운 다음 소비자 스레드에게 "대기열에 정보가 있다"는 시그널을 보낸다.
89+
90+
소비자 스레드는 대기열에서 정보를 읽어들인 후 "대기열에 빈 공간이 있다"는 시그널을 보낸다.
91+
92+
따라서 잘못하면 생산자 스레드와 소비자 스레드가 둘 다 진행 가능함에도 불구하고 동시에 서로에게서 시그널을 기다릴 가능성이 존재한다.
93+
94+
### 읽기-쓰기 (Readers-Writers)
95+
대개는 쓰기 스레드가 버퍼를 오랫동안 점유하는 바람에 여러 읽기 스레드가 버퍼를 기다리느라 처리율이 떨어진다.
96+
따라서 읽기 스레드의 요구와 쓰기 스레드의 요구를 적절히 만족시켜 처리율도 적당히 높이고 기아도 방지하는 해법이 필요하다.
97+
98+
간단한 전략은 읽기 스레드가 없을 때까지 갱신을 원하는 쓰기 스레드가 버퍼를 기다리는 방법이다.
99+
하지만 읽기 스레드가 계속 이어진다면 쓰기 스레드는 기아 상태에 빠진다.
100+
101+
반면, 쓰기 스레드에게 우선권을 준 상태에서 쓰기 스레드가 계속 이어진다면 처리율이 떨어진다.
102+
103+
양쪽 균형을 잡으면서 동시 갱신 문제를 피하는 해법이 필요하다.
104+
105+
## 동기화하는 메서드 사이에 존재하는 의존성을 이해하라
106+
동기화하는 메서드 사이에 의존성이 존재하면 동시성 코드에 찾아내기 어려운 버그가 생긴다.
107+
108+
자바 언어는 개별 메서드를 보호하는 synchronized라는 개념을 지원한다.
109+
하지만 공유 클래스 하나에 동기화된 메서드가 여럿이라면 구현이 올바른지 다시 한 번 확인하기 바란다.
110+
111+
> 권장사항: 공유 객체 하나에는 메서드 하나만 사용하라.
112+
113+
<br />
114+
115+
공유 객체 하나에 여러 메서드가 필요한 상황도 생긴다.
116+
* 클라이언트에서 잠금
117+
* 클라이언트에서 첫 번째 메서드를 호출하기 전에 서버를 잠근다.
118+
* 마지막 메서드를 호출할 때까지 잠금을 유지한다.
119+
* 서버에서 잠금
120+
* 서버에다 "서버를 잠그고 모든 메서드를 호출한 후 잠금을 해제하는" 메서드를 구현한다.
121+
* 클라이언트는 이 메서드를 호출한다.
122+
* 연결 서버
123+
* 잠금을 수행하는 중간 단계를 생성한다.
124+
* '서버에서 잠금' 방식과 유사하지만 원래 서버는 변경하지 않는다.
125+
126+
## 동기화하는 부분을 작게 만들어라

0 commit comments

Comments
 (0)