2009年5月6日 星期三

執行緒函式的設計

我在引擎的設計裡,修修改改的弄出了幾種執行緒函式的架構。

一開始是一個thread loop,執行緒函式中有一個無窮迴圈,像是我在Render Thread做的這樣,

DWORD WINAPI CRenderThread::RenderThreadFunc(LPVOID thread_obj)
{
    CRenderThread* thread_ptr=(CRenderThread*)(thread_obj);

    while (!s_bExitThread)
    {
        CRenderCommand* cmd=0;
        thread_ptr->m_CommandQueueLock.Lock();
        if (thread_ptr->m_CommandQueue.size())
        {
            cmd=thread_ptr->m_CommandQueue.front();
            thread_ptr->m_CommandQueue.pop_front();
        }
        thread_ptr->m_CommandQueueLock.Unlock();
        if (cmd)
        {
            cmd->Execute(thread_ptr->m_pRenderThreadState);
            medelete cmd;
        }

        Sleep(0);  // 這個很重要,如果不休息,主緒會忽快忽慢,而且可能兩次執行的速度差異幾十倍
    }
    return 1;
}

迴圈的最後,要讓執行緒睡一下下,因為如果不這麼做的話,在Windows的架構裡,這個執行緒可是會吃掉CPU不肯放出來的,也就會造成主緒所分配到的時間忽多忽少,跑得忽快忽慢。

另一種執行緒函式,是用在背景讀檔的執行緒,每個執行緒只執行一次。

DWORD WINAPI CThreadStream::LoadingThreadFunc(LPVOID thread_obj)
{
    CThreadStream* thread_ptr=(CThreadStream*)(thread_obj); 

    WaitForSingleObject(thread_ptr->m_BeginEvent,INFINITE);  // wait for begin 
    thread_ptr->Load(thread_ptr->m_szRequestFilename,
      thread_ptr->m_szRequestPathID);
    SetEvent(thread_ptr->m_EndEvent);  // loading done
    return 1;
}

執行緒建立之後,並不會馬上進行讀檔,而是會等待一個開始的事件,讀檔完成以後,再設定結束事件,讓主緒可以得知檔案已經讀完。

這個設計,有個問題。也就是每讀取一個檔案,我們就必須建立一個執行緒,讀完了,就刪除執行緒。這在系統資源的使用上,還有執行的效能上,都不是很好的設計方式。

我沒有用Thread Pool來改進,而是做了其他方向的修改。

最先是用了一個無窮迴圈來做。

DWORD WINAPI CThreadStream::LoadingThreadFunc(LPVOID thread_obj)
{
    CThreadStream* thread_ptr=(CThreadStream*)(thread_obj); 
    
    while (!s_bExitThread)
    {
        if (WaitForSingleObject(thread_ptr->m_BeginEvent,0)!=WAIT_OBJECT_0)
        {
            Sleep(0);
            continue;
        }
        thread_ptr->Load(thread_ptr->m_szRequestFilename,
                                      thread_ptr->m_szRequestPathID);

        SetEvent(thread_ptr->m_EndEvent);  // loading done
        ResetEvent(thread_ptr->m_BeginEvent);  // reset begin for next loop
    }
    return 1;
}

這個迴圈每次都檢查開始事件是否被設定,如果沒有,就讓執行緒睡一下下,然後繼續迴圈檢查。用這個方法,我可以讓背景讀檔的執行緒一直活著,有需要的時候,就幫忙讀個檔案。

但是這個執行緒的CPU負擔其實還是很重的,要一直looping檢查。而讀檔的工作,執行的頻率又不是很高。一直looping非常不划算。

所以我將開始事件改成了無限等待,同時加上一個離開執行緒的事件旗標。

DWORD WINAPI CThreadStream::LoadingThreadFunc(LPVOID thread_obj)
{
    CThreadStream* thread_ptr=(CThreadStream*)(thread_obj);

    HANDLE hWaitHandle[2]={ thread_ptr->m_BeginEvent,thread_ptr->m_QuitEvent };
    while (TRUE)
    {
        DWORD signal=WaitForMultipleObjects(2,hWaitHandle,FALSE,INFINITE);  // wait for begin or quit
        if (signal==WAIT_OBJECT_0+1) break;

        thread_ptr->Load(thread_ptr->m_szRequestFilename,
       thread_ptr->m_szRequestPathID);

        SetEvent(thread_ptr->m_EndEvent);  // loading done
        ResetEvent(thread_ptr->m_BeginEvent);  // reset begin for next loop
    }
    return 1;
}

這樣一來,這個執行緒只會在開始事件被設定時才會醒來工作,做完又會繼續等待。

雖然不算是最佳的設計,倒也符合我們現在的需求了。

沒有留言: