Book Record/Clean Code

[Clean Code] 13장 동시성(2)

lakelight 2022. 12. 4. 18:11
728x90
반응형

 

깨끗한 코드를 작성하기 위한 열세번째 기록

 

다중 스레드 프로그래밍에서 사용하는 실행 모델

1. 생산자-소비자

생산자 스레드가 정보를 생성해 버퍼나 대기열에 넣습니다. (버퍼와 대기열은 한정된 자원)
소비자 스레드는 대기열에서 정보를 가져와 사용합니다.
생산자 스레드는 대기열에 빈 공간이 있어야 정보를 채웁니다.
소비자 스레드는 대기열에 정보가 있어야 가져옵니다.
생산자 스레드는 대기열에 정보를 채운 다음 소비자 스레드에게 신호를 보냅니다.
소비자 스레드는 대기열에서 정보를 읽어들인 후 신호를 보냅니다.

따라서 잘못하면 생산자 스레드와 소비자 스레드가 둘다 진행 가능함에도 불구하고 동시에 서로에게서 오는 신호를 기다릴 가능성이 존재합니다.

2. 읽기-쓰기

쓰기 스레드가 버퍼를 갱신하느 동안 읽기 스레드가 버퍼를 읽지 않고, 읽기 스레드가 버퍼를 읽는 동안 쓰기 스레드가 버퍼를 갱신하지 않으려면, 균형을 잡아야 합니다.
대게 쓰기 스레드가 버퍼를 오랫동안 점유하는 바람에 여러 읽기 스레드가 버퍼를 기다리느라 처리율이 떨어집니다. 따라서 읽기 스레드의 요구와 쓰기 스레드의 요구를 적절히 만족시켜 처리율도 높이고 기아도 방지하는 해법이 필요합니다.

간단한 전략은 읽기 스레드가 없을 때까지 갱신을 원하는 쓰기 스레드가 버퍼를 기다리는 방법입니다. 하지만 읽기 스레드가 계속 이어진다면 쓰기 스레드는 기아 상태에 빠지게 됩니다. 반면, 쓰기 스레드에게 우선권을 준 상태에서 쓰기 스레드가 계속 이어진다면 처리율이 떨어집니다. 양쪽 균형을 잡으면서 동시 갱신 문제를 피하는 해법이 필요합니다.

3. 식사하는 철학자들

둥근 식탁에 철학자들이 앉아있고, 왼쪽에는 포크가 놓여있고 식탁 한가운데는 스파게티가 놓여있습니다. 배가 고프면 양손에 포크를 집어들고 스파게티를 먹습니다. 왼쪽 철학자나 오른쪽 철학자가 포크를 사용하는 중이라면 기다려야 합니다.

여기서 철학자를 스레드로, 포크를 자원으로 생각하면 많은 기업 애플리케이션에서 겪는 문제입니다. 기업 애플리케이션은 여러 프로세스가 자원을 얻으려 경쟁합니다. 주의해서 설계하지 않으면 데드락, 라이브락, 처리율 저하, 효율성 저하 등의 문제가 발생합니다.

일상에서 접하는 대다수 다중 스레드 문제는 위에 3가지 범주 중 하나에 속합니다. 각 알고리즘을 공부하고 해법을 직접 구현해보아야 합니다. 그러면 나중에 실전 문제에 부딛쳤을 때 해결이 쉬울 것입니다.

 

동기화하는 메서드 사이에 존재하는 의존성을 이해하라

동기화 하는 메서드 사이에 의존성이 존재하면 동시성 코드에 찾아내기 어려운 버그가 생깁니다. 자바에서는 개별 메서드를 보호하는 syncronized라는 개념이 존재합니다. 하지만 공유 클래스 하나에 동기화된 메서드가 여러개라면, 구현이 올바른지 확인해야 합니다.

공유 객체 하나에은 메서드 하나만 사용하는 것을 권장하지만, 공유 객체 하나에 여러 메서드가 필요한 상황도 생깁니다.

 

공유 객체 하나에 메서드가 필요한 상황에서 조치해야할 사항

1. 클라이언트에서 잠금

클라이언트에서 첫 번째 메서드를 호출하기 전에 서버를 잠급니다. 마지막 메서드를 호출할 때까지 잠금을 유지합니다.

2. 서버에서 잠금

서버에서 서버를 잠그고, 모든 메서드를 호출한 후 잠금을 해제하는 메서드를 구현합니다. 클라이언트는 이 메서드를 호출합니다.

3. 연결 서버

잠금을 수행하는 중간 단계를 생성합니다. 서버에서 잠금 방식과 유사하지만 원래 서버는 변경하지 않습니다.

 

동기화하는 부분을 작게 만들어라

자바에서 synchronized 키워드를 사용하면 락을 설정합니다. 같은 락으로 감싼 모든 코드 영역은 한 번에 한 스레드만 실행이 가능합니다. 락은 스레드를 지연시키고 부하를 가중시킵니다.

그러므로 synchronized 문을 남발하는 코드는 바람직하지 않습니다. 반면, 임계영역은 반드시 보호해야합니다. 따라서 코드를 짤 때는 임계영역 수를 최대한 줄여야 합니다.

임계 영역 수를 줄이기 위해 거대한 임계영역 하나로 구현하는 프로그래머도 있습니다. 필요 이상으로 임계 영역을 키우게 되면 스레드 간에 경쟁이 늘어나고 프로그램 성능이 떨어집니다.

 

올바른 종료 코드는 구현하기 어렵다

깔끔하게 종료하는 코드는 올바로 구현하기 어렵습니다. 가장 흔히 발생하는 문제가 데드락입니다. 즉 스레드가 절대 오지 않을 시그널을 기다리는 상황입니다.

예를 들면, 부모 스레드가 자식 스레드를 여러 개 만든 후 모두가 끝나기를 기다렸다 자원을 해제하고 종료하는 시스템에서 자식 스레드 중 하나가 데드락에 걸렸다면 부모 스레드는 영원히 기다릴 것입니다. 그러므로 깔끔하게 종료하는 다중 스레드 코드를 짜야 한다면 시간을 투자해 올바르게 구현해야 합니다.

 

스레드 코드 테스트하기 위한 지침

1. 말이 안되는 실패는 잠정적인 스레드 문제로 취급하라
2. 다중 스레드를 고려하지 않은 순차 코드부터 제대로 돌게 만들자.
3. 다중 스레드를 쓰는 코드 부분을 다양한 환경에 쉽게 끼워 넣을 수 있도록 스레드 코드를 구현하라.
4. 다중 스레드를 쓰는 코드 부분을 상황에 맞춰 조정할 수 있게 작성하라.
5. 프로세서 수보다 많은 스레드를 돌려보라.
6. 다른 플랫폼에서 돌려보라.
7. 코드에 보조 코드를 넣어 돌려라. 강제로 실패를 일으키게 해보라.

 

 

[출처]
Clean Code (클린 코드,애자일 소프트웨어 장인 정신), 로버트 C. 마틴 저

 

728x90
반응형

'Book Record > Clean Code' 카테고리의 다른 글

[Clean Code] 13장 동시성(1)  (4) 2022.11.29
[Clean Code] 12장 창발성  (2) 2022.11.25
[Clean Code] 11장 시스템  (2) 2022.11.19
[Clean Code] 10장 클래스  (0) 2022.11.06
[Clean Code] 9장 단위 테스트  (2) 2022.10.24