스레드가 하나인 프로그램은 무엇과 언제가 서로 밀접하다.
동시성은 바로 이 무엇과 언제를 분리하여 결합을 없애는 전략이다.
또한 응답 시간과 작업 처리량 개선을 위해서이기도 하다.
이렇듯 동시성은 다양한 장점을 가지고 있지만, 이해 관한 오해도 존재한다.
이번엔 반대로 동시성과 관련된 타당한 생각들이다.
Lock 으로 인한 오버헤드, context switching 정도를 말하는게 아닐까 생각되네요.
일어날 때도 있고 일어나지 않을 때도 있기 때문에..
public class X {
private int lastIdUsed;
public int getNextId() {
return ++lastIdUsed;
}
}
두 스레드가 위 클래스의 같은 변수를 동시에 참조하는 경우
lastIdUsed 가 42이고 두 개의 스레드가 한 번씩 증가를 시켜도 44가 아닌,
43이 되는 경우가 발생할 수도 있다. 두 스레드가 자바 코드 한 줄을 거쳐가는 경로는 수없이 많기 때문이다.
(책에선 잠재적으로 12,870개라고 언급)
대다수의 경우 올바른 결과를 내놓지만, 문제는 잘못된 결과를 내놓는 '일부 경로'가 존재한다는 것이다.
동시성 코드가 일으키는 문제로부터 시스템을 방어하는 원칙과 기술들
동시성은 복잡성 하나 만으로도 분리할 이유가 충분하다.
동시성 관련 코드는 다른 코드와 분리해야 한다는 뜻이다.
자료를 캡슐화하고 공유 자료를 최대한 줄여라
여기저기 산발적으로 퍼져있을 수록, 많을 수록 빼먹기 쉽기 때문
객체를 복사해 읽기 전용으로 사용할 수 있는 경우라면 그렇게 하는 것이 좋다.
복사 비용 < Lock 으로 인한 비용
(가능하면) 다른 스레드와 자료를 공유하지 않는 것이 좋다.
멀티스레드에 대해 다루는 전통적인 실행 모델들을 다룬다.
생산자는 empty.acquire() // # of permit = BUF_SIZE
즉, 버퍼가 가득 차면 기다리고
소비자는 full.acquire() // # of permit = 0
버퍼가 비면 기다린다.
서로에게 시그널을 보내어 락을 해제하므로
잘 못 설계하면 동시에 서로에게서 시그널을 기다릴 가능성이 존재한다.
쓰기 스레드가 버퍼를 갱신하는 동안 읽기 스레드가 버퍼를 읽지 않으려면,
읽기 스레드가 버퍼를 읽는 동안 쓰기 스레드가 버퍼를 갱신하지 않으려면,
복잡한 균형잡기가 필요하다.
읽기 스레드에게만 우선권을 부여하면 쓰기 스레드는 기아 상태에 빠질 것이다.
반면 쓰기 스레드에게만 우선권을 부여하면 처리율이 떨어질 것이다.
즉, 동시 갱신 문제를 회피하면서도 양쪽 균형을 잡을 수 있는 해법이 필요하다.
둥근 식탁에 철학자 한 무리가 둘러앉아있다.
각 철학자 왼쪽에는 포크가 놓였다. 스파게티를 먹으려면 양 손에 포크를 들어야 하기 때문에 누군가는 먹지 못하는 상황이 생긴다.
잘 설계하지 않으면 데드락, 처리율 저하를 겪게 된다.
일상에서 접하는 대다수의 멀티 스레드 문제는 위 세 범주 중 하나에 속한다.
각 알고리즘을 공부하고 해법을 이해하면 잘 대처할 수 있다.
공유 객체 하나에는 메서드 하나만 사용하라.
공유 객체 하나에는 여러 메서드가 필요한 상황도 생기는데 그런 경우에는,
Lock은 스레드를 지연시키고 부하를 가중시킨다. 그러므로 여기저기서 Lock 을 남발하는 코드는 바람직하지 않다.
임계영역은 최대한 줄여야 한다.
ex) 부모 스레드가 모든 스레드에게 종료하라는 시그널을 보냈는데
자식 스레드 중 생산자-소비자 관계가 있는 경우
생산자 스레드는 종료했는데 소비자 스레드가 생산자 스레드에서 오는 메시지를 기다리는 상태라면 ?
소비자 스레드는 block 상태이므로 부모 스레드로부터 종료하라는 시그널을 받지 못한다.
소비자 스레드는 생산자 스레드를 영원히 기다리고, 부모 스레드는 소비자 스레드를 영원히 기다린다.
위와 같은 다양한 문제들이 있기 때문에
깔끔하게 종료하는 코드는 올바르게 구현하기 어려우므로
시간을 많이 투자해 구현하고 이미 나온 알고리즘을 검토하라.
멀티 스레드 상황에서의 테스트 코드는 단일 스레드의 테스트코드와 다르다.
문제를 노출하는 테스트케이스를 작성하고 설정과 부하를 바꿔가며 자주 돌려라
테스트가 실패하면 원인을 추적하고 다시 돌렸더니 통과하더라는 이유로 넘어가면 안된다.
즉 고려할 것이 많다는 것.
생산자-소비자, 읽기-쓰기, 식사하는 철학자와 같은 케이스