VC++技術內幕(第四版)筆記(第11章)

/******************************************/
第十一章:Winsows消息處理和多線程編程


一)Winsows消息處理

1,單線程:程序代碼只有一條執行路徑。

2,單線程程序消息處理過程:
MSG message;
while(::GetMessage(&message,NULL,0,0))
{
::TranslateMessage(&message);//翻譯如wm_char消息
::DispatchMessage(&message);//把消息分發給指定窗口的回調函數
}
說明:
1)os決定哪個消息屬於我們的程序,當一個消息要處理的時候,用GetMessage函數返回該消息。
2)如果沒有屬於我們程序的消息發出,則我們的程序被掛起,而其它的程序可以運行;當屬於我們程序的消息到達,我們的程序被喚醒

3,改造2中的單線程消息處理過程:
問題一:某個消息控制函數很“笨重”,消耗CPU時間過久。
問題二:DispatchMessage函數要等到該笨重消息控制函數返回後才能返回。在該笨重消息控制函數返回之前DispatchMessage不處理(分發)任何消息。
解決方法:使用PeekMessage函數使佔用CPU時間過久的消息控制函數每隔一段時間交出控制一次。(在佔CPU時間過久的控制函數中利用PostMessage發送
代碼如下:
MSG message;
while(::PeekMessage(&message,NULL,0,0,PM_REMOVE))
{
::TranslateMessage(&message);//翻譯如wm_char消息
::DispatchMessage(&message);//把消息分發給指定窗口的回調函數
}
說明:
//The PeekMessage function checks a thread message queue for a message and places the message (if any) in the specified structure.
簡要說明:
1)BOOL PeekMessage(
  LPMSG lpMsg,         // pointer to structure for message
  HWND hWnd,           // handle to window
  UINT wMsgFilterMin,  // first message
  UINT wMsgFilterMax,  // last message
  UINT wRemoveMsg      // removal flags
);
//If a message is available, return nonzero,otherwise return 0;
//Unlike the GetMessage function, the PeekMessage function does not wait for a message to be placed in the queue before returning.
//PeekMessage retrieves only messages associated with the window identified by the hWnd parameter or any of its children as specified by the IsChild function, and within the range of message values given by the wMsgFilterMin and wMsgFilterMax parameters.
//If hWnd is NULL, PeekMessage retrieves messages for any window that belongs to the current thread making the call.
//If hWnd is –1, PeekMessage only returns messages with a hWnd value of NULL, as posted by the PostThreadMessage function.
//If wMsgFilterMin and wMsgFilterMax are both zero, PeekMessage returns all available messages (that is, no range filtering is performed).
//PeekMessage does not retrieve messages for windows that belong to other threads.

2)BOOL GetMessage(
  LPMSG lpMsg,         // address of structure with message
  HWND hWnd,           // handle of window
  UINT wMsgFilterMin,  // first message
  UINT wMsgFilterMax   // last message
);
//GetMessage does not retrieve messages for windows that belong to other threads or applications.
//

4,計時器獨立於CPU時鐘速度。
1)計時器的使用:用一個時間間隔參數調用CWnd::SetTimer函數,然後再用ClassWizard爲WM_TIMER添加消息控制函數。
2)一旦調用CWnd::SetTimer啓動了計時器,則WM_TIMER消息會被持續發送到指定的窗口,直至調用CWnd::KillTimer函數或計時器窗口被取消爲止。
3)由於WINDOWS非實時OS,如果時間間隔少於100毫秒,計時器可能不準。
4)OS發送計時器消息的時候,如果隊列裏已經有了計時器消息,則不會把同樣的計時器消息放進消息隊列裏。
說明:
1)CWnd::SetTimer
UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );
//Return The timer identifier of the new timer if the function is successful. An application passes this value to the KillTimer member function to kill the timer. Nonzero if successful; otherwise 0.
//nIDEvent:Specifies a nonzero timer identifier.
//nElapse:Specifies the time-out value, in milliseconds.
//lpfnTimer:Specifies the address of the application-supplied TimerProc callback function that processes the WM_TIMER messages. If this parameter is NULL, the WM_TIMER messages are placed in the application’s message queue and handled by the CWnd object.
//CWnd::SetTimer Installs a system timer. A time-out value is specified, and every time a time-out occurs, the system posts aWM_TIMER message to the installing application’s message queue or passes the message to an application-defined TimerProc callback function.
2)CWnd::KillTimer
BOOL KillTimer( int nIDEvent );
//Return value:Return nozero if the event is killed.It is 0 if the KillTimer member function could not find the specified timer event.
//CWnd::KillTimer Kills the timer event identified by nIDEvent from the earlier call to SetTimer. Any pending WM_TIMER messages associated with the timer are removed from the message queue.


5,在引入多線程編程之前,WINDOWS程序員曾使用空狀態處理來完成一些後臺任務。(這裏是OnIdle函數)
1)應用程序框架在框架消息處理循環中調用CWinApp::OnIdle(虛函數),可以重載它來處理後臺任務。通常,一但OnIdle函數完成了它的工作,就要等到下次應用程序消息隊列空了後才被調用。基類的OnIdle會更新工具欄按鈕和狀態欄指示器,並清除各種臨時對象指針。
2)重載OnIdle函數可以用來更新用戶界面,很有實際意義。(注:在重載OnIdle函數中一定要調用基類的OnIdle函數哦)
3)如果用戶運行在一個模式對話框或正在使用菜單,則這時OnIdle函數不被調用。但我們可以在框架類中添加WM_ENTERIDLE消息控制函數來在上敘情況下使用後臺處理。(注意,這裏是在框架類中添加,不是在視圖類中。彈出式對話框中是屬於應用程序主框架窗口。)

///////
///////

二,多線程編程:

1,一些概念:
1)一個進程就是一個運行的程序,進程具有獨立的內存、文件句柄和其它系統資源。
2)一個獨立的進程可以包含多條執行路徑,稱爲線程。線程有OS管理,每個線程有自己的堆棧。
3)大多數情況下,進程的所有代碼和數據空間被進程內所有的線程所共享。
4)Windows提供兩中線程:輔助線程 和 用戶界面線程。mfc對兩種線程都支持。
5)一個用戶界面線程有窗口,因而有自己的消息循環;輔助線程沒有窗口,因而不需要消息處理。

2,啓動輔助線程:
準備工作:寫一個全局函數。格式:UINT MyControllingFunction( LPVOID pParam );
啓動輔助線程:使用AfxBeginThread函數爲全局函數MyControllingFunction創建線程。
說明一:
MFC 通過參數重載提供兩個版本的 AfxBeginThread:一個用於用戶界面線程,另一個用於輔助線程。
//用戶界面線程通常用於處理用戶輸入和響應用戶事件,這些行爲獨立於執行該應用程序其他部分的線程。已經創建並啓動主應用程序線程(在 CWinApp 導出的類中提供)。
//輔助線程通常用於處理後臺任務,用戶不必等待就可以繼續使用應用程序。重新計算和後臺打印等任務是很好的輔助線程示例。
說明二:
創建用戶界面線程比創建輔助複雜(需要重寫一些相關的函數),這裏只筆記創建輔助線程一些筆記。
1)AfxBeginThread
CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
//Return Value:Pointer to the newly created thread object.
//pfnThreadProc:Points to the controlling function for the worker thread. Cannot be NULL. This function must be declared as follows:    UINT MyControllingFunction( LPVOID pParam );
//pParam:Parameter to be passed to the controlling function as shown in the parameter to the function declaration in pfnThreadProc.
//nPriority:The desired priority(優先權) of the thread. If 0, the same priority as the creating thread will be used. For a full list and description of the available priorities, seeSetThreadPriority in the Win32 Programmer’s Reference.
//nStackSize:Specifies the size in bytes of the stack for the new thread. If 0, the stack size defaults to the same size stack as the creating thread.
//dwCreateFlags:Specifies an additional flag that controls the creation of the thread. This flag can contain one of two values:
[CREATE_SUSPENDED]   Start the thread with a suspend count of one. The thread will not execute until ResumeThread is called.
[ 0  ]  Start the thread immediately after creation.
//lpSecurityAttrs:Points to a SECURITY_ATTRIBUTES structure that specifies the security attributes for the thread. If NULL, the same security attributes as the creating thread will be used.
//Remarks:
Call this function to create a new thread. The first form of AfxBeginThread creates a worker thread. The second form creates a user-interface thread.
To end the thread, call AfxEndThread from within the thread, or return from the controlling function of the worker thread.
2)掛起線程:
CWinThread::SuspendThread
DWORD SuspendThread( );
//Return Value:The thread’s previous suspend count if successful; 0xFFFFFFFF otherwise.
//CWinThread::SuspendThread:Increments the current thread’s suspend(懸掛,延緩) count. If any thread has a suspend count above zero, that thread does not execute.
3)恢復線程運行:
CWinThread::ResumeThread
DWORD ResumeThread( );
//Return Value:The thread’s previous suspend count if successful; 0xFFFFFFFF otherwise. If the return value is zero, the current thread was not suspended. If the return value is one, the thread was suspended, but is now restarted. Any return value greater than one means the thread remains suspended.
//Remarks:Called to resume(恢復) execution of a thread that was suspended(暫停,延緩) by the SuspendThread member function, or a thread created with the CREATE_SUSPENDED flag. The suspend count of the current thread is reduced by one. If the suspend count is reduced to zero, the thread resumes execution; otherwise the thread remains suspended.
4)創建輔助線程事例:
UINT MyThreadProc( LPVOID pParam )
{
    CMyObject* pObject = (CMyObject*)pParam;
    if (pObject == NULL ||
        !pObject->IsKindOf(RUNTIME_CLASS(CMyObject)))
    return 1;   // if pObject is not valid
    // do something with 'pObject'
    return 0;   // thread completed successfully
}
// inside a different function in the program
...
pNewObject = new CMyObject;
AfxBeginThread(MyThreadProc, pNewObject);

3,主線程 和 輔助線程 的通話:(這裏的主線程指應用程序,是個用戶界面線程)
1)最簡單的方法:使用全局變量。
(注意:如書上事例B中在一個輔助線程中使用全局的計數器,不希望其它現在在計數器遞增的時候由於其它線程訪問而引起混亂,則將起聲明成volatile變量保證計數器不被保存到寄存器中,也可以使用InterlockedIncrement來阻塞其它線程同時使計數器遞增。)
補充一:
InterlockedIncrement
LONG InterlockedIncrement(
  LPLONG lpAddend   // pointer to the variable to increment
);
//The InterlockedIncrement function both increments (increases by one) the value of the specified 32-bit variable and checks the resulting value. The function prevents more than one thread from using the same variable simultaneously.
補充二:
volatile
使用 volatile 修飾符能夠確保一個線程檢索由另一線程寫入的最新值。
//當字段聲明中含有 volatile 修飾符時,該聲明引入的字段爲易失字段。

由於採用了優化技術(它會重新安排指令的執行順序),在多線程的程序運行環境下,如果不採取同步控制手段,則對於非易失字段的訪問可能會導致意外的和不可預見的結果。這些優化可以由編譯器、運行時系統或硬件執行。但是,對於易失字段,優化時的這種重新排序必須遵循以下規則:

讀取一個易失字段稱爲易失讀取。易失讀取具有“獲取語義”;也就是說,按照指令序列,所有排在易失讀取之後的對內存的引用,在執行時也一定排在它的後面。
寫入一個易失字段稱爲易失寫入。易失寫入具有“釋放語義”;也就是說,按照指令序列,所有排在易失寫入之前的對內存的引用,在執行時也一定排在它的前面。
這些限制能確保所有線程都會觀察到由其他任何線程所執行的易失寫入(按照原來安排的順序)。一個遵循本規範的實現並非必須做到:使易失寫入的執行順序,在所有正在執行的線程看來都是一樣的。易失字段的類型必須是下列類型中的一種:
引用類型。

類型 byte、sbyte、short、ushort、int、uint、char、float 或 bool。
枚舉基類型爲 byte、sbyte、short、ushort、int 或 uint 的枚舉類型。
(China msdn-C# 語言規範 )

2)不能使用Windows消息(即不能主線程向輔助線程發送消息通信),輔助線程沒有窗口沒有消息循環。


4,輔助線程 和 主線程(用戶界面線程) 通信
1)輔助線程向主線程(用戶界面線程)發送Windows消息,由主線程響應該消息,從而實現通信。(主線程有一個窗口,可見或不可見,如果輔助線程可以得到主線程的窗口句柄,便可以向主線程發送Windows消息了。主線程總是有一個消息循環的。)
2)輔助線程可以通過AfxBeginThread函數參數傳入主線程句柄從而得到主線程的句柄。
3)輔助線程使用寄出(post)消息。任何用戶定義的消息都可以。(使用送出(SEND)消息會引起主線程MFC消息處理代碼的重入,這在模式對話框中會出現問題。)


5,EX11B事例說明:
1)CComputeDlg::OnStart函數中,利用AfxBeginThread(ComputeThreadProc, GetSafeHwnd(),THREAD_PRIORITY_NORMAL);函數爲用戶自定義的全局函數ComputeThreadProc創建輔助線程的同時,利用GetSafeHwnd()獲得對話框句柄並做爲參數傳入
ComputeThreadProc函數形參pParam中。
//GetSafeHwnd() Returns the window handle for a window. Returns NULL if the CWnd is not attached to a window or if it is used with a NULL CWnd pointer.
2)UINT ComputeThreadProc(LPVOID pParam)函數中利用傳進來的參數pParam,調用::PostMessage((HWND) pParam, WM_THREADFINISHED, 0, 0)函數向對話框窗口發送消息用戶自定義WM_THREADFINISHED消息。
3)在對話框類中爲WM_THREADFINISHED添加控制函數。
三步:
消息控制函數聲明:CComputeDlg類頭文件 LRESULT OnThreadFinished(WPARAM wParam, LPARAM lParam);消息映射:CComputeDlg類代碼文件 ON_MESSAGE(WM_THREADFINISHED, OnThreadFinished)
消息控制函數:CComputeDlg類代碼文件
LRESULT CComputeDlg::OnThreadFinished(WPARAM wParam, LPARAM lParam)
{
 CDialog::OnOK();
 return 0;
}

6,排斥區(CCriticalSection)
MFC提供了CCriticalSection類來幫助我們實現在線程之間共享全局數據(保證對其臨界訪問).
使用方法下面代碼演示:
CCriticalSection g_cs;//定義g_cs爲臨界訪問對象
int g_nCount;
voit func()
{
 g_cs.Lock();
 g_nCount++;
 g_cs.Unlock();
}
說明:
1)CCriticalSection從CSyncObject類派生而來:
An object of class, CCriticalSection represents a "critical section" - a synchronization object that allows one thread at a time to access a resource or section of code. Critical sections are useful when only one thread at a time can be allowed to modify data or some other controlled resource.
2)構造函數CCriticalSection( )說明:
Constructs a CCriticalSection object. To access or release a CCriticalSection object, create a CSingleLock object and call its Lock and Unlock member functions. If the CCriticalSection object is being used stand-alone, call its Unlock member function to release it.
3)CCriticalSection::Unlock:  Releases the CCriticalSection object.
CCriticalSection::Lock: Use to gain access to the CCriticalSection object.
4)進一步使用說明:
當線程A正在執行func()函數使g_nCount++增1的時候,線程B調用func()函數執行到g_cs.Lock()的時候線程B被阻塞直到線程A執行了g_cs.Unlock()才繼續往下執行g_nCount++。
5)CCriticalSection只是用於當個進程內的控制訪問,如果要在不同的進程之間控制數據的訪問需要使用 互斥體(CMutex) 和 信號(semaphore)。


7,互斥體(CMutex)
1)CMutex:
An object of class CMutex represents a “mutex” — a synchronization object that allows one thread mutually exclusive access to a resource. Mutexes are useful when only one thread at a time can be allowed to modify data or some other controlled resource.
2)CMutex類從CSyncObject類派生而來,其一般用法參見下E文:
To use a CMutex object, construct the CMutex object when it is needed. Specify the name of the mutex you wish to wait on, and that your application should initially own it. You can then access the mutex when the constructor returns. Call CSyncObject::Unlock when you are done accessing the controlled resource.


8,信號(CSemaphore)
1)CSemaphore(也是從CSyncObject類派生而來):
An object of class CSemaphore represents a “semaphore” — a synchronization object that allows a limited number of threads in one or more processes to access a resource. A CSemaphore object maintains a count of the number of threads currently accessing a specified resource.
2)Semaphores are useful in controlling access to a shared resource that can only support a limited number of users.The current count of the CSemaphore object is the number of additional users allowed. When the count reaches zero, all attempts to use the resource controlled by the CSemaphore object will be inserted into a system queue and wait until they either time out or the count rises above 0. The maximum number of users who can access the controlled resource at one time is specified during construction of the CSemaphore object.
構造函數:
CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );

9,關於從CSyncObject類派生類一些說明:
1)CCriticalSection,CMutex,CSemaphore,CEvent,derived from CSyncObject。
2)CSyncObject類成員中包含一下兩成員函數:
Lock   Gains access to the synchronization object.
Unlock   Releases access to the synchronization object.


////////////////
筆記後語:多線程編程是個大塊的內容。這些書上只略概了些,筆記作用不是很大,詳見MSDN和專門指導書更有實踐性。日後等俺功力到火候了,一定做些專題性的總結筆記。

////////////////////
//////////////////

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章