메시지 펌프 (Message Pumps)
멀티쓰레딩에 관해 프로그래머가 가지는 일반적인 오해는 멀티쓰레딩이 애플리케이션을 더 빠르게 만든다는 생각이다. 싱글-프로세서 머신의 경우, 그렇지 않다. 하지만, 애플리케이션의 응답성은 높여준다. 멀티쓰레딩이 만들 수 있는 응답성에 관한 차이점을 설명하는 한가지 방법은 메뉴 명령에 따라 수 천개의 타원을 그리는 애플리케이션을 작성하는 것이다. 만약 주 쓰레드에 의해 그림이 그려지고 주 쓰레드는 자신의 메시지 큐를 검사하지 않고 어떤 대기 메시지도 처리하지 않는다면 그리는 루프가 종료될 때까지 입력이 얼어버릴 것이다. 다른 쓰레드에서 그림을 그리도록 동일한 애플리케이션이 작성된다면, 그리는 루프가 실행되는 동안 사용자 입력에 대해 계속해서 응답할 것이다.
하지만, 이와 같이 간단한 시나리오에서는 멀티쓰레딩은 약간 과잉이다. 다른 해결책은 주 쓰레드가 타원을 그리는 동안 메시지가 계속해서 흘러가도록 메시지 펌프를 사용하는 것이다. 아래와 같이 그림을 그리는 메시지 핸들러를 가정하라.
void CMainWindow::OnStartDrawing () { for (int i=0; i<NUMELLIPSES; i++) DrawRandomEllipse (); } |
NUMELLIPSES 가 만약 큰 수라면, 일단 for 루프가 시작되면 프로그램은 오랫동안 멈출 수 있다. 아래처럼, for 루프를 정지시키는 플래그를 설정하는 다른 메뉴 커맨드를 추가할 수 있다.
void CMainWindow::OnStartDrawing () { m_bQuit = FALSE; for (int i=0; i<NUMELLIPSES && !m_bQuit; i++) DrawRandomEllipse (); } void CMainWindow::OnStopDrawing () { m_bQuit = TRUE; } |
하지만 동작하지 않을 것이다. 왜 그럴까? 메시지를 펌프하지 않으면 OnStartDrawing 에서 for 루프가 실행되는 한 OnStopDrawing 을 활성화시키는 WM_COMMAND 메시지를 받을 수 없기 때문이다. 사실상, 메뉴는 for 루프가 실행되는 동안에는 풀다운 메뉴가 펼쳐질 수 조차 없다.
이 문제는 메시지 펌프를 이용하여 쉽게 해결된다. 아래는 싱글-쓰레드 MFC 프로그램에서 시간이 꽤 걸리는 프로시져를 실행하는 적절한 방법의 예이다.
void CMainWindow::OnStartDrawing () { m_bQuit = FALSE; for (int i=0; i<NUMELLIPSES && !m_bQuit; i++) { DrawRandomEllipse (); if (!PeekAndPump ()) break; } } void CMainWindow::OnStopDrawing () { m_bQuit = TRUE; } BOOL CMainWindow::PeekAndPump () { MSG msg; while (::PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) { if (!AfxGetApp ()->PumpMessage ()) { ::PostQuitMessage (0); return FALSE; } } LONG lIdle = 0; while (AfxGetApp ()->OnIdle (lIdle++)); return TRUE; } |
PeekAndPump 는 메시지 루프내에서 메시지 루프를 재현한다(enact). OnStartDrawing 의 for 루프내에서 각 반복이 끝나는 시점에 호출되면서, PeekAndPump 는 큐에 메시지가 대기하고 있다고 ::PeekMessage 가 가리킨다면 메시지를 가져와 처리하기 위해 먼저 CWinThread::PumpMessage 를 호출한다. PumpMessage 가 0을 반환하는 것은 마지막 메시지를 얻었고 WM_QUIT 메시지가 처리되었다는 것을 의미한다, 그리고 WM_QUIT 메시지는 주 메시지 루프(main message loop)가 가져가지 않으면 애플리케이션은 종료되지 않기 때문에 특별한 처리를 위해 ::PostQuitMessage 를 호출한다. 이것이 바로 PumpMessage 가 0을 반환하면 PeekAndPump가 큐에다가 또다른 WM_QUIT 메시지를 포스트하는 이유이며, 그리고 PeekAndPump 가 0을 반환하면 OnStartDrawing 에서 for 루프를 종료하는 이유이기도 하다. 만일 WM_QUIT 메시지가 조기에 종료시키지 않는다면, PeekAndPump 는 함수를 반환하기 전에 애플리케이션 객체의 OnIdle 함수를 호출함으로써 프레임워크의 idle 매커니즘을 시뮬레이션 한다.
그리기 루프에 삽입된 PeekAndPump를 이용하여, OnStopDrawing 을 활성화 시키는 WM_COMMAND 는 정상적으로 얻어지고 처리된다. OnStopDrawing 은 m_bQuit 를 TRUE로 설정하기 때문에, 그리기 루프는 다음 타원이 그려지기 전에 종료될 것이다.
참고 : Programming with MFC 2nd Edition