" 아무 생각 없이 스레드를 사용하는 것은 매우 나쁜 습관! "

독립적인 테스크를 동시에 수행하기 위해 개발자는 보통 스레드를 선택하게 된다. 스레드는 프로세스에 비해 저렴하며, 자바를 이용하는 프로그래머에게는 매우 쉽게 동시성을 지니게 할 수 있는 무기이다. 보통의 경우 스레드를 사용하는 입장에서는 공용 데이터에 대한 동기화에만 목메달고 밤잠을 설치기 마련이다.

하지만 과연 동기화만 해결되면 그것으로 스레드는 안전한 것일까? 대답은 물론 '아니요'가 되겠다. 스레드의 동시성은 사실 보장할 수 없다. 간단한 예제 프로그램을 생각해보자. 스레드의 테스크는 루프가 수행된 횟수를 저장한다. 이러한 스레드를 수십~수만개를 생성해서 일정 시간 수행시키고 난 후 각 스레드로부터 그 값을 출력해보면 그 분포가 가희 예술적이다. 개발자는 그것이 모두 같은 숫자이기를 기대하겠지만, 혹은 조금 더 양보하여 비슷한 숫자를 기대하겠지만 실제 결과는 판이하게 다를 수 있다.

이것은 매우 심각한 문제이다. 분명 다수의 스레드가 서로 동시에 진행된 것은 사실이나 누구는 이쁨받아 많이 수행되고 누구는 미움받아 조금 수행되는 것은 전혀 기대하지 않은 에러일 것이다.

무엇이 문제인가?

나는 10개 내외의 소수의 스레드를 운영하면서도 이러한 결과를 본 적이 있다. 이 때 스레드 내부의 모습을 보면 무한 루프를 돌면서 각각의 테스크를 수행하고 있는데, 무한 루프의 스레드일 경우 주의해야 할 사항이 있다. 다음의 코드를 보자.

public void run() {
    while(true) {
        System.out.println("Hello World");
    }
}


위 코드를 갖는 스레드를 열댓개 생성해 수행시키면 앞서 말한대로 누구는 이쁨받고 누구는 미움받는다. 위의 코드는 시스템 리소스를 거의 독점하다시피 동작한다. 이런 경우 다음과 같은 간단한 해결책을 제시할 수 있다.

public void run() {
    while(true) {
         System.out.println("Hello World");
         try {
              Thread.sleep(1);
         } catch(InterruptedException e) {}
    }
}


추가된 코드의 Thread.sleep(delay)는 delay만큼 스레드를 대기시킨다. 단위는 밀리세컨드(ms). 위 코드는 고작 1ms 동안 대기 상태에 빠지지만 이것으로 인해 각 스레드는 시스템 리소스를 독점하는 일이 현저히 줄어든다. 어리숙한 내 견해로는 OS의 Context Switch 가 일어날 수 있는 시점을 제공했기 때문이라고 생각된다.

물론 위 코드는 문제에 대한 근본적인 해결책은 아니다. 소수의 스레드를 운영하는 경우 위의 간단한 코드 수정으로 인해 심각한 문제를 조금 덜 심각한 문제로 내릴 수 있다는 것이다. 감기약이랑 비슷하다. 그렇다면 정말 근본적인 문제를 해결할 수 있는 방법은 없는가?

불행히도 없다. 위의 문제는 분명히 스레드의 동작 특성때문에 생긴 어쩔 수 없는 문제이다. 근본적인 문제를 잡자면 스레드를 쓰지 않는 것 외에는 딱히 방법이 없을 지도 모른다. 하지만 문제를 조금 덜 심각하게 떨어뜨리는 방법은 있다.

1) 우선 스레드의 수를 줄여라.
스레드의 숫자가 많아짐은 위의 문제를 점점 심각하게 만드는 것이다. 정말로 필요로하는 부분에만 스레드를 사용하라. 스레드를 쉽게 만들 수 있다고 해서 마구잡이로 스레드 수를 늘리지 마라.

2) 적절히 대기(Wait) 상태를 사용할 것.
어떤 메세지가 왔는지 확인하기 위해 폴링을 사용하는 스레드는 시스템 리소스를 독점하다시피 동작한다. CPU 점유율은 위로 치솟고 다른 스레드들이 버벅이는 모습이 눈에 훤할 것이다. 이럴때엔 스레드를 대기 상태로 놓고 새로운 메세지가 왔을 경우 깨워주는 테크닉을 활용하자. 적절한 대기 상태는 처음의 프로그램이 CPU를 100% 사용하던 것을 2% 이내로 줄여줄 수 있다.

3) 적절히 Thread.sleep()을 사용할 것.
단 1ms라도 sleep이 걸리면 그 스레드가 시스템 리소스를 독차지 하는 행동을 제지할 수 있다. 있는 것과 없는 것의 차이는 그야말로 천지 차이. 적절히 사용하자. 대기 상태가 없는 무한 루프의 스레드가 다수인 경우 sleep값을 조정하다보면 만족할 만한 결과에 근접할 수 있다. (만족할 만한 결과는 결코 되지 않는다)


스레드에 대한 맹목적인 믿음은 시스템을 신뢰할 수 없게 만든다. 스레드를 얼마나 사용하는가는 시스템의 기초적인 설계 방향을 결정하는 중요한 요소이기 때문에 사용하기 전에 신중히 검토하고 고려해야 한다. 가능하면 스레드를 사용하지 마라. 이것은 동기화에 대한 압박으로부터 벗어날 수 있으며 시스템의 안전성을 크게 향상시킬 수 있다. 스레드를 사용할 수 밖에 없다면 동기화에 대해서는 두말 할 것도 없고, 앞서 설명한 지침을 따를 것! 스레드를 맹목적으로 믿다간 전체 시스템이 흔들리는 결과를 초래할 수 있으니 항상 주의해야한다.
이 글의 트랙백 주소는 http://semix2.tistory.com/trackback/135 입니다