제가 프로그래밍을 처음 배울 때의 CLI 프로그래밍과 WIN32 프로그래밍으로 넘어왔을 때 큰 괴리를 느꼈습니다.
그 이유는 바로 EVENT-DRIVEN(message based)프로그래밍 때문이었죠.
현재는 reactor라는 패턴이란 이름으로 더 알려진 이 메시지 기반 프로그래밍은, DOS 시절의 동기 프로그래밍에 익숙한 많은 프로그래머를 괴롭게 했습니다.
message라는걸 왜 굳이 만들어 처리하는가….에 대해 저는 그 당시 이해하기 어려웠습니다. 당시만해도, 윈도우 메시지를 굳이 처리하지 않고도 여러 작업이 가능했기 때문이죠.
예를 들어 GetAsyncKeyState 같은 함수로도 얼마든지 키 입력 처리가 가능했고, 마우스 입력도 마찬가지였죠.
굳이 왜 메시지 핸들러 코딩을 왜 해줘야 하는지 이해가 잘 안 갔어요. 특히나 윈도우 기본 컨트롤들을 사용하지 않았기 때문이기도 할 겁니다.
이 때가 98년 경이었는데, COM 기반의 메소드를 통해 초기화하던 Direct-X가 이해 안갔던… (그 당시엔 COM이 뭔지도, 왜 저렇게 복잡하게 포인터 캐스팅하는지도 이해가 어려웠습니다) 시기였습니다.
코어도 한 개이던 시절이라, multithread 프로그래밍은 n개의 CPU를 꽂아 쓰는 서버나 슈퍼 컴퓨터 사용자들에게나 필요한 걸로 치부 되기도 했습니다.
헌데…그 어렵디 어렵다던 multithread 프로그래밍이 사실은 WIN32 프로그래밍에선 각종 API를 통해 이루어지고 있었고, 메시지 핸들링 코드는 다른 thread (다른 프로세스에서 오는 메시지도 있지만)에서의 처리 결과를 전달하는 코드이기도 했다는 걸 한참이 지나서야 알게 됐습니다.
더 깊게 알아내지 못한 제 잘못도 있지만, 책과 PC통신을 제외하곤 정보를 얻기 힘든 당시 상황을 감안하면, 좀 더 친절하게 설명해주지 못한 당시 WIN32 프로그래밍 입문서들의 문제도 있습니다.
자! 여기서 핵심은, WIN32 어플리케이션을 만들어온 과정이 사실은 multithread 프로그래밍을 해왔다라는 점이에요. 우리가 몰랐을 뿐이죠.
모를 수 있었다는 것은, 사실 WIN32 메시지 핸들러를 통한 thread 모델이 안정적이고, 잘 디자인된 thread design중 하나라고 볼 수 있습니다. WIN32 메시지 핸들링 방식은 메시지 루프를 통해 비동기 작업들을 처리해야 될 때에만 양보하고 다시 주 thread 루프로 돌아오게 해주었던 안정적인 thread design과 API를 구성했죠.
이로써, multithread에 대한 이해 없이, 그리고 thread 동기화에 신경 쓰지 않고도 비동기 프로그래밍을 할 수 있었습니다.
그렇습니다! 우리는 WIN32 메시지 기반 프로그래밍을 통해 Reactor thread design을 경험한 것입니다!
정리하자면, thread design이란 다음 요구사항을 충족해야 한다고 보시면 됩니다.
객체를 lock을 신경 써야 될 부분, 그렇지 않은 부분을 명확히 규정
- 프레임워크단에서만 신경쓰도록 구현하는 것을 권장하지만, 개체 단위 lock이 정책인 경우도 있습니다.
- 개체 단위 lock으로 구현한 경우에는, 코딩 난이도 상승, 병목 지점 감지도 어려워질 뿐더러, 데드락 위험성도 높아져 좋지 않은 디자인이라고 봅니다.
API 사용법만 준수하면 최대한 많은 코딩 영역에 thread 동기화를 고민하지 않도록 하는 것
- 이 부분이 사실 핵심인데, thread design이 잘 되어 있을 수록, 성능도 만족하면서 유지 보수 난이도가 급격하게 낮아진다고 보시면 됩니다.
Blocking 동작이나, 반복작업, 병렬 수행이 필요한 작업등을 지원하기 위해 비동기 작업을 위임하거나, 분산할 수 있는 기능
- thread pool을 통한 작업 분배, open MP등으로 지원된 병렬 코드 수행등을 떠올리시면 좋습니다. WIN32 메시지 기반 모델에서는 병렬 작업을 위한 분산 기능은 미흡했으나, (시기적인 부분을 감안해 줘야 된다고 생각합니다만) 그 이외의 요구 사항은 충족합니다.
이렇듯 thread design이란 먼 곳에 있지 않았습니다!