4 주차
수 | Assignment #19
13장. 동시성
📘 책에서 기억하고 싶은 내용
- 동시성이 필요한 이유 (p.226)
- 동시성은
무엇
과 언제
를 분리하는 결합을 제거하는 전략
- 스레드가 하나일 경우
무엇
과 언제
는 밀접함
- 동시성 방어 원칙 (p.230)
- 단일 책임 원칙(SRP)
- 공유 자료 범위의 제한
- 임계영역을 보호하기 위해
synchronized
키워드를 사용하지만 가급적 자료를 캡슐화하여 공유 자료를 최소화해야 함
- 공유 자료 대신 자료 사본 사용
- 처음부터 자료를 공유하는 대신, 객체 사본을 사용하는 등으로 공유 객체를 피할 있다면 피해야 함
- 공유 객체를 피한다면 코드가 문제를 일으킬 가능성도 아주 낮아짐
- 스레드는 독립적으로 구현
- 독자적인 스레드로 다른 프로세서에서도 돌려도 괜찮도록 자료를 독립적인 자료로 분할해야 함
- 라이브러리를 이해하라 (p.232)
- 스레드 구현 시
thread-safe
이 보장된 컬렉션을 사용해야 함
- 실행 모델을 이해하라 (p.233)
- 각 모델 및 알고리즘을 공부하고 해법을 직접 구현해보아야 함
- 접하는 대다수 다중 스레드 문제는 아래 세 범주 중 하나에 속함
- 생산자-소비자
생산자
---정보를 채움---> [대기열 (버퍼 or 큐, 한정된 자원)]
---정보를 사용---> 소비자
- 대기열을 사용하고자 생산자/소비자 스레드는 서로에게 시그널을 보내는데, 잘못할 경우 둘 다 진행 가능함에도 동시에 서로에게서 시그널을 기다릴 가능성이 존재함
- 읽기-쓰기
- 읽기 스레드를 위한 주된 정보원으로 공유 자원을 사용하지만, 쓰게 스레드가 공유 자원을 필요 시 갱신함
- 처리율을 강조하면 기아 현상이 생기거나 오래된 정보가 쌓이고, 갱신을 자주 허용하면 처리율에 영향을 미침
- 식사하는 철학자들
- 둥근 식탁에 철학자들이 앉는데, 각 철학자 왼쪽에는 포크가 놓여 있고, 식탁 가운데에 스파게티 한 접시가 놓여 있음. 배가 고프면 양 손에 포크를 집어들고 스파게티를 먹는데, 양쪽 중 어느 한 포크가 사용중이라면 포크를 내려 놓을 때까지 기다려야 함.
- 철학자를 스레드로, 포크를 자원으로 바꿔 생각하면 스레드 사용 시 발생하는 문제임
- 여러 프로세스가 자원을 얻으려 경쟁하는데, 주의해서 설계하지 않을 경우 데드락, 라이브락, 처리율 저하 등을 겪음
- 동기화하는 메소드 사이에 존재하는 의존성을 이해하라 (p.235) >> 다시 봐야 함
- 동기화하는 메소드에 의존성이 존재하면 동시성 코드에 찾아내기 어려운 버그가 발생함
- 공유 객체 하나에 메소드 하나만 사용하는 것을 권장함
- 메소드 하나만 사용할 수 없을 경우 아래 방법을 고려해봐야 함
- 클라이언트에서 잠금 : 클라이언트에서 첫 번째 메소드를 호출하기 전에 서버를 잠금, 마지막 메소드를 호출할 때까지 잠금 유지
- 서버에서 잠금 : 서버를 잠그고 서버의 모든 메소드 호출 후 잠금을 해제하는 메소드 구현, 클라이언트는 이를 호출
- 연결 서버 : 잠금을 수행하는 중간 단계 생성, 원래 서버는 변경하지 않음
- 동기화하는 부분을 작게 만들어라 (p.236)
- 임계영역 개수를 줄이기 위해 거대한 임계영역을 만들지 않아야 함
- 필요 이상으로 임계영역 크기를 키우면 스레드 간 경쟁이 늘어나 성능이 저하됨
- 올바른 종료 코드는 구현하기 어려움 (p.236)
- 종료 코드를 개발 초기부터 고민하고, 초기부터 구현해야 함
- 이미 나온 알고리즘을 검토하여 구현해야 함
- 스레드 코드 테스트 (p.237)
- 개요
- 스레드 코드가 올바르다고 증명하기는 현실적으로 불가능하지만, 충분한 테스트는 위험을 낮춤
- 문제를 노출하는 테스트 케이스를 작성하고, 프로그램 설정과 시스템 설정과 부하를 바꿔가며 자주 실행해야 함
- 테스트가 실패하면 원인을 추적해야 하며, 다시 실행하고 통과했다는 이유로 그냥 넘어가서는 안 됨
- 구체적인 지침
- 말이 안 되는 실패는 잠정적인 스레드 문제로 취급하라
- 오류 재현이 어렵더라도 시스템 실패를 일회성이라 치부하면 안 됨
- 다중 스레드를 고려하지 않은 순차 코드부터 제대로 돌게 만들자
- 스레드 환경 밖에서 우선 코드가 올바르게 동작하는지 확인해야 함
- 스레드 환경 밖에서 생기는 버그와 스레드 환경에서 생기는 버그를 동시에 디버깅하면 안 됨
- 다중 스레드를 쓰는 코드 부분을 다양한 환경에 쉽게 끼워 넣을 수 있도록 스레드 코드를 구현하라
- 적절한 스레드 개수를 파악하려면 상당한 시행착오가 필요한데, 이를 조율하기 쉽게 코드를 구현해야 함
- 다중 스레드를 쓰는 코드 부분을 상황에 맞춰 조정할 수 있게 작성하라
- 적절한 스레드 개수를 알아내려면 상당한 시행착오가 필요한데, 이를 위해 스레드 개수를 조율하기 쉽게 코드를 작성해야 함
- 프로세서 수보다 많은 스레드를 돌려보라
- 시스템이 스레드를 스와핑할 때도 문제가 발생하는데, 스와핑이 잦을수록 임계영역을 빼먹은 코드나 데드락을 일으키는 코드를 찾기 쉬워짐
- 다른 플랫폼에서 돌려보라
- 다중 스레드 코드는 플랫폼에 따라 다르게 돌아가, 코드가 돌아갈 가능성이 있는 플랫폼 전부에서 테스트를 수행해야 함
- 처음부터 그리고 자주 모든 목표 플랫폼에서 코드를 실행해야 함
- 코드에 보조 코드를 넣어 돌리고, 강제로 실패를 일으키게 해보라
- 직접 구현 : 코드에 직접
wait()
, sleep()
, yield()
, priority()
함수 추가, 까다로운 코드 테스트 시 적합
- 자동화 :
AOF
, CGLIB
, ASM
등과 같은 도구를 사용하여, 무작위로 sleep
이나 yield
를 호출
- 결론 (p.243)
SRP
를 준수하여 POJO
를 사용해 스레드를 아는 코드와 스레드를 모르는 코드로 분리
- 동시성 오류를 일으키는 잠정적인 원인을 철저히 이해해야 함
- 사용하는 라이브러리와 기본 알고리즘을 이해해야 함
- 보호할 코드 영역을 찾아내는 방법과 특정 코드 영역을 잠그는 방법을 이해해야 함
- 공유하는 객체 수와 범위를 최대한 줄여야 함
🤔 소감 및 생각
- 최근에는 멀티 스레드 프로그래밍을 할 일이 많지가 않아 오랜만에 스레드 관련 내용을 보니 새로운 느낌이었다. 중요한 개념을 까먹지 않도록 스레드를 다시 공부해야겠다고 다짐하게 되었다. 스레드를 공부한 뒤 다시 이 챕터를 읽으면 느낌이 새로울 것 같았다.
🔍 새롭게 또는 다시 알게 된 내용