파비의 매일매일 공부기록

파이썬 동시성 프로그래밍 - #4 스레드 간 동기화 본문

Study/Python

파이썬 동시성 프로그래밍 - #4 스레드 간 동기화

fabichoi 2021. 8. 11. 23:30

이번장에서는 멀티스레드를 사용할 때의 기본적인 동기화 primitive에 대해 알아본다.

 

앱의 성능을 높이고자 스레드를 추가하는 것만으로는 충분치 않다.

그러므로 경합 조건 같은 복잡한 경우를 고려하고 코드도 적절하게 작성해야 한다.

 

스레드 간 동기화

- 철학자의 저녁식사 문제(교착상태) : 5명의 철학자가 둥근 탁자에 앉아서 스파게티를 먹는 문제이다. 5개의 포크가 있으나 스파게티를 먹기 위해서는 2개의 포크를 사용해야 한다. 5명 모두가 전부 왼쪽(혹은 오른쪽) 포크를 사용할 때 문제가 생긴다. 아무도 먹을 수가 없다. 이는 동기화 프리미티브(락)에 의지하는 동시성 시스템을 디자인할 때 발생하는 문제를 나타낸다. 포크는 시스템 자원이고 각 철하자는 프로세스라고 보면 된다.

- 경합 조건 : 시스템의 출력이 제어할 수 없는 이벤트의 연속 및 타이밍에 의지하는 상태를 의미. 은행계좌를 예로 들면, 100만 원을 넣어놨는데 그에 대한 이자 10만 원이 들어온다고 하자. 인출과 입금 프로세스가 다르다고 할 때 이자 10만 원이 계좌에 들어가기 전에 계좌에서 100만 원이 인출된다면, 이자는 지급되지 않을 수 있다. 이를 해결하기 위해 계좌 인출 및 갱신 코드, 입금 및 갱신 코드를 묶어 처리하도록 하며 인출 및 갱신 혹은 입근 및 갱신 시 락을 걸어 다른 프로세스가 실행되지 않게 해결할 수 있다. 

- 위험영역 : 공유 자원을 수정하고 이에 접근하는 모든 코드의 위험 영역(critical section)에 대해 알아본다. 위험 영역은 어떠한 경우에서는 한 번에 한 개의 프로세스만 실행 가능하다. 예기치 못한 에러를 찾으려 할 때 위험 영역에 넣어 테스트한다. 파일 시스템 및 생명과 직결되는 시스템에서는 위험영역 테스트가 필요할 수 있다.

 

공유 자원과 데이터 경합 : 동시성 앱을 구현할 때 경합 조건은 꼭 주의해야 한다. 수정하기 힘든 버그로 이어질 수 있으며 앱에 치명적인 영향을 줄 수 있다. 

- join 메서드 : 스레드가 종료될 때까지 부모 스레드가 더 진행되지 않게 막아줌. 자연적으로 종료 상태가 되거나 처리되지 않은 예외 경우를 발생 시킴.

- lock : 다수의 스레드 실행으로 공유 자원에 접근할 때 필요한 메커니즘. 화장실 문을 잠그는 것과 비슷한 동기화 프리미티브로 '락' 및 '언락' 상태가 존재하며 '언락' 상태에서만 '락'을 요청 가능

- R락 : 리엔트런트(한 번 로드하면 여러 가지 메인 루틴에서 자유롭게 사용할 수 있는 구조) 락, R락은 일반적인 락 프리미티브에서 작동하는 동기화 프리미티브. 스레드가 이미 갖고 있다고 여러 번 요청됨. 다른 클래스 메서드에 접근하는 클래스 내의 메서드에 스레드 세이프 접근을 이용할 때 쓸모가 있음. 일반적인 락이 교착상태를 유발할 수 있는 반면, R락은 릴리즈 메커니즘이 존재해서 교착상태가 발생하지 않는다.

- 컨디션 : 다른 스레드의 신호를 기다리는 동기화 프리미티브. 해당 스레드가 실행을 마쳐야만 현재 스레드가 나머지 계산을 수행할 수 있음.

- 세마포어 : 요청 및 릴리즈 호출이 있을 때마다 증감되는 내부 카운터. 초기화된 카운터는 다른 세팅이 없으면 기본적으로 1. 세마포어 카운터는 음수가 될 수 없음.

- 한정된 세마포어 : 현재 값이 초기값을 초과하는지 확인. 초과하지 않으면 ValueError 발생. 대부분의 상황에서 세마포어는 한정된 능력으로부터 자원을 보호. 일반적으로 수많은 사람이 한곳에 집중되거나 특정 작업을 한 번에 수행하는 등 자원 소비를 막고자 할 때 사용됨.

- 이벤트 : 동시적으로 실행되는 여러 스레드 사이의 간단한 통신 형태에 유용. 일반적으로 한 스레드가 다른 스레드의 신호를 받아들일 때 이벤트가 발생. 참/거짓이 가능한 내부 플래그로 구성된 객체가 필수로 존재. 스레드는 이벤트 객체의 상태를 계속 확인하며 언제 어떤 방식으로 플래그 상태를 바꿀지 정함.

- 배리어 : 컨디션 및 세마포어의 복잡한 조합으로 해결된 문제를 처리. 모든 스레드 내의 작업이 스레드 그룹에 의해 생성됐는지 보장하는 중요한 부분. 복잡하고 불필요하게 보일 수 있으나 특정 상황에서는 꼭 필요하고 코드 가독성도 높임.

 

스레드 관련 용어가 엄청 많이 나온 챕터였다.

몇 개는 이미 익숙했던 용어긴 한데.. 아마 오늘 공부한 것의 반 정도는 까먹지 않을까 싶다 ㅎㅎㅎ

덤으로 테스트해볼 소스도 꽤 많다. (각 용어에 대한 상세한 설명을 위한 example이 있음)

반응형
Comments