미신과 오해
다음은 동시성과 관련한 일반적인 미신과 오해이다.
반대로 다음은 동시성과 관련된 타당한 생각 몇 가지다.
두 스레드가 같은 변수를 동시에 참조하면 이상한 결과가 발생할 수 있다..재수없게 동시에 참조하지 않는다면 올바른 결과를 내놓겠지만, 문제는 잘못된 결과를 내놓는 일부 경우이다.
하지만 동시성 코드가 일으키는 문제로부터 시스템을 방어하는 원칙과 기술들이 있다. (아래에 서술)
단일 책임 원칙
동시성은 복잡성 하나만으로도 따로 분리할 이유가 충분하다. 동시성 하나만으로도 충분히 어렵기 때문에 주변의 다른 코드가 발목잡지 않도록 하자. 또한 동시성 코드는 독자적인 개발,변경,조율 주기가 있으므로 분리해야 마땅하다.
따름 정리: 자료 범위를 제한하라
동일 필드를 수정하던 두 스레드가 서로 간섭해 이상한 결과를 내놓는 걸 방지하기 위해... 임계영역(critical section)을 synchronized
키워드로 보호하는 것이 좋다.
따름 정리: 자료 사본을 사용하라
공유 자료를 줄이려면 처음부터 공유하지 않는 방법이 제일 좋다. 객체를 복사해 읽기 전용으로 사용하는 방법 등이 사용된다.
따름 정리: 스레드는 가능한 독립적으로 구현하라
각 스레드는 클라이언트 요청 하나를 처리한다. 모든 정보는 비공유 출처에서 가져오며 로컬 변수에 저장한다. 그러면 스레드는 다른 스레드와 동기화할 필요가 없고, 자신만 생각하며 돌아갈 수 있다.
언어가 제공하는 동기화 관련 함수, 클래스 등을 잘 이해하고 써먹자
생산자-소비자
인벤토리라는 버퍼나 큐 변수가 있을때....소비자 스레드가 인벤에서 아이템을 가져오고, 생산자 스레드가 아이템을 인벤에 넣는다고 치자. 여기서 인벤토리는 크기가 정해져 있다. 생산자는 인벤에 빈 공간이 있을때만 아이템 넣을 수 있고, 소비자는 인벤에 아이템이 있어야만 가져갈 수 있다.
생산자 스레드는 '인벤에 아이템이 있다(=니가 가져갈게 있다.)' 라는 시그널을 보내고, 소비자 스레드는 '인벤에 빈 공간이 있다(=니가 넣을 자리가 있다.)' 시그널을 보낸다.
하지만 잘못하면 생산자 스레드와 소비자 스레드가 둘 다 진행 가능함에도 서로에게서 시그널을 기다리고만 있을 가능성이 존재한다.
읽기-쓰기
읽기만 하는 쓰레드와 쓰기만 하는 스레드가 있을때, 대개는 쓰기 스레드가 버퍼를 오랫동안 점유하는 바람에 여러 읽기 스레드가 버퍼를 기다리느라 처리율이 떨어진다. 그렇다고 읽기 스레드만 먼저 처리하면 쓰기 스레드에 기아현상이 발생할 수 있다.
식사하는 철학자들
철학자들이 원탁에 둘러앉아 있다. 철학자는 양손에 포크를 쥐어야만 먹을 수 있다. 만약 모든 철학자가 왼쪽 포크만 잡은 상태라면, 철학자들은 계속 기다리기만 해야 할 것이다..
공유 객체 하나에는 메서드 하나만 사용하는 게 좋다.
..하지만 공유 객체 하나에 여러 메서드가 필요한 상황도 생긴다. 그럴때는 아래 3가지 방법을 고려한다.
synchronized
문을 남발하는 코드는 좋지 않다. critical section 수를 줄인답시고 거대한 critical section 하나를 만드는 프로그래머도 있는데 매우 나쁜 예이다. 동기화하는 부분은 최대한 작게 만들어라.
깔끔하게 종료하는 코드는 구현하기 어렵다. 가장 흔히 발생하는 문제는 데드락(스레드들이 절대로 오지 않는 시그널을 기다리며 무한 대기하는 현상)이다. 그러므로 종료하는 부분을 구현할땐 시간을 투자해 꼼꼼하게 구현하자.
다시 돌렸더니 통과했다 등의 안일한 태도로 넘어가면 안된다. 스레드 코드 테스트는 고려할 게 많다. 아래에 몇 가지 구체적인 지침이 있다.