现在我们已经有了三个可独立工作的线程:资源加载线程、逻辑线程、渲染线程,下一步我们需要决定它们如何在实际的项目中相互配合,也就是所谓的应用程序框架了,该框架需要解决以下两个问题
首先,资源读取线程可以简单设计为一个循环等待的线程结构,每隔一段时间检查加载队列中是否有内容,如果有则进行加载工作,如果没有则继续等待一段时间。这种方式虽然简单清晰,但却存在问题,如果等待时间设得过长,则加载会产生延迟,如果设得过短,则该线程被唤醒的次数过于频繁,会耗费很多不必要的CPU时间。
然后,主线程是逻辑线程还是渲染线程?因为逻辑线程需要处理键盘鼠标等输入设备的消息,所以我起初将逻辑线程设为主线程,而渲染线程另外创建,但实际发现,帧数很不正常,估计与WM_PAINT消息有关,有待进一步验证。于是掉转过来,帧数正常了,但带来了一个新的问题,逻辑线程如何处理键盘鼠标消息?
对于第一个问题,有两种解决方案:
第一,我们可以创建一个Event,资源读取线程使用WaitForSingleObject等待着个Event,当渲染线程向加载队列添加新的需加载的资源后,将这个Event设为Signal,将资源读取线程唤醒,为了安全,我们仍需要在渲染线程向加载队列添加元素,以及资源加载线程从加载队列读取元素时对操作过程加锁。
第二,使用在渲染线程调用PostThreadMessage,将资源加载的请求以消息的形式发送到资源价值线程,并在wParam中传递该资源对象的指针,资源加载线程调用WaitMessage进行等待,收到消息后即被唤醒,这种解决方案完全不需要加锁。
对于第二个问题,我们同样可以用PostThreadMessage来解决,在主线程的WndProc中,将逻辑线程需要处理的消息发送出去,逻辑线程收到后进行相关处理。
需要注意的是,我们必须搞清楚线程是在何时创建消息队列的,微软如是说:
The thread to which the message is posted must have created a message queue, or else the call to PostThreadMessage fails. Use one of the following methods to handle this situation.
- Call PostThreadMessage. If it fails, call the Sleep function and call PostThreadMessage again. Repeat until PostThreadMessage succeeds.
- Create an event object, then create the thread. Use the WaitForSingleObject function to wait for the event to be set to the signaled state before calling PostThreadMessage. In the thread to which the message will be posted, call PeekMessage as shown here to force the system to create the message queue.
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE)
Set the event, to indicate that the thread is ready to receive posted messages.
看来,我们只需要在线程初始化时调一句PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE)就可以了,然后在主线程中如此这般:
switch ( uMsg )
{
case WM_PAINT:
{
hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
{
m_pLogic->StopThread();
WaitForSingleObject( m_pLogic->GetThreadHandle(), INFINITE );
PostQuitMessage(0);
}
break;
default:
{
if ( IsLogicMsg( uMsg ) )
{
PostThreadMessage( m_pLogic->GetThreadID(), uMsg, wParam, lParam );
}
else
{
return DefWindowProc( hWnd, uMsg, wParam, lParam );
}
}
break;
}
在逻辑线程中这般如此:
MSG msg;
while ( m_bRunning )
{
if ( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
{
if ( ! GetMessageW( &msg, NULL, 0, 0 ) )
{
return (int) msg.wParam;
}
MessageProc( msg.message, msg.wParam, msg.lParam );
}
LogicTick();
}
完成!