참고로... 이글은 마소 2007년 3월에 서광열님(kwangyul.seo@gmail.com)께서 기재하신 기사를 제가 이해하는 과정입니다....
ㅡㅅ-);; 무조건 따라서 치면서 머리 속에 담는 것이지요....
-------------------------------------------------------------------
멀티 코어시대에 대비한 소프트웨어기술
병렬(parallel/concurrent) 프로그래밍
하드웨어가 변하고 있다. 듀얼코어(Dual Core) 시스템이 일반 데스크탑에 탑재되고 있고, 프로세서가 4개인
쿼드코어(Quad Core)도 서버 머신에서 사용되기 시작했다. 고사양 서버 컴퓨터뿐만 아니라, 임베디드 시스템
에도 여러 개의 코어를 사용하는 경우가 늘고 있다. 대표적인 예제가 ARM11의 MPCore 기술인데, 1-4개의
코어를 사용할 수 있다. 하드웨어 단에서 멀티코어의 등장은 개발자들에게도 변화를 요구하기 시작했다. 하부의
여러 프로세서를 좀 더 적극적으로 활용하기 위해 멀티스레드 프로그램이 더욱 중요한 요소로 주목받기 시작한
것이다. 하지만 멀티스레드 프로그램은 작성하기 어렵고 테스트와 디버깅은 더 어렵기로 악명 높다.
멀티 코어 시대에 대비하는 소프트웨어 기술은 무엇인지 살펴보자.
멀티 코어 시대의 소프트웨어
멀티 코어 시대는 멀티스레드 프로그래밍을 요구한다. 프로세스 벤더들이 과도한 열 발생 문제 때문에 CPU클럭
경쟁에 한계를 느끼고, 멀티코어 아키텍처로 전환하기 시작한 시점부터 소프트웨어 개발자들도 이 변화에 동참
하기를 요구받고 있다. 과거 프로세서의 최적화 기술인 인스트럭션 레벨 병렬화(Instruction Level Parallelism)가
소프트웨어의 변화 없이 하드웨어의 성능 향상을 추구했다면, 현재의 멀티 코어 아키텍처는 성능 향상을 위해
소프트웨어 개발자의 적극적인 참여를 필요로 한다.
즉 과거는 하드웨어가 발전하면 소프트웨어는 가만히 앉아서 성능 향상의 이득을 누렸지만, 앞으로는 소프트웨어가
적극적으로 하드웨어의 병렬성(parellelism)을 이용하지 않는 이상 추가적인 성능향상을 기대하기 어렵다는 말이다.
이런 상황에서 전문가들은 병렬 혁명(Concurrent revolution)은 하드웨어가 아닌 소프트웨어에서 나올 것이라고
말하고 있다. 실제로 기술적인 관점에서 멀티 코어 하드웨어를 만드는 일은 크게 어렵지 않다고 한다. 문제는 현재의
프로그래밍 패러다임으로는 멀티 코어를 제대로 활용하는 멀티스레드 소프트웨어를 작성하기가 쉽지 않다는 것이다.
조금만 복잡해지면 버그 투성이가 되고 마는 멀티스레드 프로그램이기 때문이다. 하지만 그럼에도 불구하고 이제
소프트웨어 개발자는 코어가 여러 개 있다는 가정 하에 명시적으로 이를 활용하는 소프트웨어를 작성해야 한다.
앞으로는 병렬 프로그래밍은 개발자들의 선택이 아닌 필수가 될 것이다.
멀티스레드 프로그래밍의 어려움
하지만 멀티스레드 프로그래밍은 개발자의 골칫거리다. 스레드 숫자가 늘어날수록 프로그램의 복잡도는 급속히
상승한다. 멀티스레드 프로그램은 디버깅도 어렵다. 멀티스레드 프로그래밍은 프로그램 수행이 불확정적(nondeter- minstic)이다. 다시 말해서, 같은 프로그램이라도 매번 실행 때마다 미묘하게 타이밍이 바뀐다. 이런
특성 때문에 멀티스레드 고유의 버그인 데드락(Deadlock), 라이브락(Livelock), 레이스컨디션(Race condition)은
디버깅하기도 테스트하기도 쉽지 않다. 테스트할 때는 멀쩡하게 잘 되다가 실제로 배포되고 나면 말썽을 일으켜서
개발자들을 괴롭힌다.
현재 멀티스레드 프로그램은 스레드를 새로 생성하고, 뮤텍스(Mutex, mutual exclusion), 컨디션 변수(Conditional
variable)등 기초적인 요소를 이용해 작성한다. 하지만 뮤텍스나 컨디션 변수는 너무 하위 레벨의 스레드 제어 도구라서
올바른 멀티스레드 프로그램을 작성하기는 무척 어렵다. 공유 자원에 접근할 때마다 매번 뮤텍스를 잡았다 풀어야 하고,
이 과정에서 한 번이라도 뮤텍스 unlock을 빠뜨리거나 순서를 잘못 정하면 데드락이 된다. 결국 현재의 패러다임으로는
올바른 멀티스레드 프로그램을 만들 수 있기를 기대하기가 어렵다.
흔히들 자바의 등장이 멀티스레드 프로그래밍을 대중화시켰다고 한다. C/C++프로그램이 pthread 같은 외부 라이브러리
에 의존해야만 멀티스레드 프로그래밍을 할 수 있는 반면에 자바는 멀티스레드 프로그래밍을 언어에 내재시켰다. 자바는
멀티스레드 프로그래밍을 언어에 내재시켰다. 자바에서는 java.lang.Thread 클래스로 쉽게 새로운 클래스를 만들 수 있고,
synchronized 같은 키워드를 통해 모니터를 잡을 수 있다. java.nio가 나오기 전에 자바는 모든 IO가 블로킹(Blocking)이
었기 때문에 스레드의 사용이 매우 보편화되기에 이른다.
하지만 여기서 생각해 보아야 할 점은 스레드를 쉽게 만들 수 있는 것과 멀티스레드 프로그램 버그 없이 쉽게 작성할 수
있는 것 사이에는 큰 차이가 있다는 점이다. 멀티스레드 프로그래밍을 제대로 이해하지 못한 일련의 자바 개발자들은
데드락과 레이스 컨디션으로 얼룩진 엉터리 멀티스레드 프로그램을 양산해내기 시작했기 때문이다. 이런 자바 프로그램은
개발자가 테스트할 때만 잘 동작한다는 특징을 가진다.
제대로된 멀티스레드 프로그램을 작성하기 어려운 이유가 무엇일까? 현재의 멀티스레드 프로그래밍 모델을 묘사한 말
중에 "바닥에 포도 주스 쏟지 마라(Don't spill grape juice on the carpet)"는 말이 있다. 풀어서 말하면, Lock, Unlock을
제대로 하고, 컨디션 변수에 제대로 wait, signal을 날려주는 것은 절대적으로 개발자의 몫이고 여기서 발생하는 실수를
방지하기 위한 어떠한 안전장치도 제공해주지 않는다는 것이다. 포도 쥬스를 쏟지말라(뮤텍스를 제대로 써라)라는 원칙만
있지 포도 쥬스가 쏟아지는 일(데드락, 레이스 컨디션의 발생)을 막을 장치는 하나도 없는 것이다.
자바 멀티스레드 프로그래밍에 대한 새로운 지평을 연 책인 "Java Concurrency in Practice" [1] 에 대한 다음과 같은
자바 코드 예제가 나온다.
<멀티스레드 환경에서 문제가 되는 코드>
public class UnsafeSequence {
private int value;
/** Returns a unique value **/
public int getNext() {
return value++;
}
}
이 프로그램을 이용해 고유한 ID(Unique ID)를 생성한다면 단일 스레드에서는 문제없이 동작하지만 멀티스레드 환경에서는
문제가 발생한다. 두 스레드가 동시에 Value의 값을 읽은 후에 value +1을 value에 저장할 경우 두 스레드가 같은 값을
읽어갈 수 있기 때문이다. 이런 버그가 생기는 이유는 value++ 이란 코드가 원자성(atoicity)을 보장해주지 않기 때문이다.
이 코드는 하나의 명령으로 보이지만 실제로는 (1)value의 메모리 값을 읽어오는 일과 (2) value + 1을 메모리에 쓰는 2가지
일로 이루어져 있고 (1)과 (2) 사이에 컨텍스트 스위치(Context Switch)가 일어나 다른 스레드가 끼어들 여지가 있다.
멀티스레드 프로그램 버그를 만드는 또 다른 원인으로는 가시성(Visibility)이 있다. 현재의 CPU는 캐시(Cache)를 가지고
있고, 멀티코어의 경우는 각각의 CPU가 캐시를 가진다. 이런 상황에서 한 스레드가 바꾼 메모리의 값을 다른 스레드가
곧바로 볼 수 있다는 보장을 하기가 힘들다. 가시성은 한 스레드 변경한 메모리의 값이 다른 스레드에 어떤 시점에 보여야
하는지를 명확히 제어하려는 시도이다.
멀티스레드 프로그램 버그를 만드는 또 다른 원인으로는 가시성(Visibility)이 있다. 현재의 CPU는 캐시(Cache)를 가지고
있고, 멀티 코어의 경우는 각각의 CPU가 캐시를 가진다. 이런 상황에서 한 스레드가 바꾼 메모리의 값을 다른 스레드가
곧바로 볼 수 있다는 보장을 하기가 힘들다. 가시성은 한 스레드 변경한 메모리의 값이 다른 스레드에 어떤 시점에 보여야
하는지를 명확히 제어하려는 시도이다.
자바에서는 원자성과 가시성을 보장하기 위해 Synchronized 키워드를 사용한다. 문제는 자바의 Synchronized 키워드는
오용하기 무척 쉽다는 점이다. 오용 사례는 다음과 같다.
(1) 동기화해야 하는 리소스에 대해 synchronized 사용하는 것을 빠뜨린 경우
(2) 잘못된 리소스에 synchronized 를 사용한 경우
(3) synchronized 를 너무 빈번히 사용하는 경우
(1), (2)번 사례의 문제점은 쉽게 알 수 있고, (3)의 경우는 성능과 관련이 있다. 모든 리소스에 락을 걸어서 사용하면
멀티스레드 버그를 피할 수는 있다. 하지만 모든 리소스에 락을 걸면 사실상 동시에 하나의 스레드만 동작할 수 있으므로
실제로는 멀티스레드가 아닌 단?嬋볜뭇 프로그램이나 마찬기자기 되는 문제가 있다. 멀티 코어의 시대가 왔을 ??도 이런
멀티스레드 프로그래밍 패러다임을 이용해서는 하부 하드웨어의 성능을 100% 끌어내기가 힘들다. 우리가 작성한
프로그램은 데드락과 레이스 컨디션으로 오작동하는 프로그램이 되거나 멀티코어를 제대로 활용 못하는 싱글스레드에
가까운 프로그램이 되는 두 운명의 갈래에서 서성일 것이다.
To be continue....
----------------------------------------------------------------------------------------------------------------
내가 생각하는 내가 나아갈 방향은... 서버 + 네트워크 + 프로그래밍... 그 중심에는 멀티스레드 프로그래밍이...
중심이 되지 않을까라는 생각을 하고 있다. 아마도... 미래의 컴퓨터들은 고클럭+다중코어 체제로 들어설 것이다.
이를 효율적으로 사용할 수 있는 능력을 키운다면... 충분히 내 위치를 확보할 수 있을 거라고 생각한다.
우선은 내가 해야할 부분은... 네트워크 쪽이 될 것으로 보인다.... ^^;;
일단은 이 바닥...(ㅡㅅ-);; 사촌형이 이바닥이라고 하지말라는데... 후후~~)을 잘 디뎌서 이 세상으로 뛰쳐나갈
준비를 하고 있습니다. ^3^ 우후후후~~ 덤벼라 세상아!!?
관련링크 :
병렬(parallel/Concurrent) 프로그래밍 2/3