MFC不能多執行緒操作控制元件的原因

xianjian_x發表於2016-05-17

  對於大多數mfc物件,請不要線上程間傳遞它們,不管是棧上的還是堆上的!原因如下:

  mfc的大多數類不是執行緒安全的,呼叫傳入物件的成員函式可能不會報錯,但是未必能達到程式預定的功能!

  mfc與介面有關的類,其大多數成員方法都是通過sendmessage實現的,如果訊息處理函式本身不是執行緒安全的,你從工作執行緒中呼叫這些方法遲早會同你介面執行緒的使用者訊息響應發生衝突;

  對於CWnd相關的類,即使傳入視窗控制程式碼,有時操作也會引起異常(ASSERT異常):通過控制程式碼獲取視窗物件並且呼叫其成員函式或者成員變數!因為該物件是臨時物件,訪問其成員變數沒有意義,訪問其成員函式可能會丟擲異常!

  不能線上程間傳遞mfc物件的詳細分析很麻煩,涉及到MFC程式中的三種狀態:模組狀態、程式狀態以及執行緒狀態!

  下面轉載了相關的文章,原文:《MFC多執行緒程式設計》

表現——錯誤示例

關於啟動執行緒時傳輸視窗物件(指標?控制程式碼?)的問題:

在選擇選單中的開始執行緒後:

void   cmainframe::onmenu_start()  
{
    ...  
    afxbeginthread(mythread,   this);  
    ...  
} 

執行緒函式如下:

uint   mythread(lpvoid   pparam)  
{
    cmainframe*   pmainfrm   =   (cmainframe   *)pparam;  
    ...  
}

問題一:
  這樣的程式碼是不是有問題? (文件中說執行緒間不能直接傳輸mfc物件的指標,應該通過傳輸控制程式碼實現)

問題二:
  這樣使用開始好像沒有問題,直接通過pmainfrm訪問視窗中的view都正常。 但發現訪問狀態條時:

    pmainfrm->m_wndstatusbar.setpanetext(2,   "test);  

出現debug assertion failed!(在視窗執行緒中沒有問題) 位置是wincore.cpp中的

    assert((p   =   pmap->lookuppermanent(m_hwnd))   !=   null   ||  
    (p   =   pmap->lookuptemporary(m_hwnd))   !=   null);  

  為什麼訪問view能正常,但訪問狀態條時不可以呢?

問題三:
  如果通過傳輸控制程式碼實現,怎樣做呢?我用下面的程式碼執行時有問題:

void   cmainframe::onmenu_start()  
{  
    ...  
    hwnd   hwnd   =   getsafehwnd();  
    afxbeginthread(mythread,   hwnd);  
    ...  
}  

uint   mythread(lpvoid   pparam)  
{  
    cmainframe*   pmainfrm   =   (cmainframe   *)(cwnd::fromhandle((hwnd)pparam));  
     ...  
}  

  執行時通過執行緒中得到pmainfrm,訪問其成員時不正常。

網友:hewwatt大致原因解釋如下:

  mfc的大多數類不是執行緒安全的,cwnd及其訊息路由是其中之最
  mfc介面類的大多數方法,最後都是通過sendmessage實現的,而訊息處理的過程中會引發其他訊息的傳送及處理。如果訊息處理函式本身不是執行緒安全的你從工作執行緒中呼叫這些方法遲早會同你介面執行緒的使用者訊息響應發生衝突。
  cxxxx::fromhandle會根據呼叫者所線上程查表,如果查不到使用者建立的cxxxx對應物件,它會建立一個臨時物件出來。由於你在工作執行緒中呼叫該方法,當然不可能查到介面主執行緒中你所建立起來的那個物件了。這時mfc會你建立一個臨時物件並返回給你,你根本不可能期望它的成員變數會是有意義的。 所以要用 也只能用cwnd::fromhandle,因為它只包含一個m_hwnd成員。 不過,要記住跨執行緒直接或間接地呼叫::sendmessage,通常都是行為不可預測的。

原因分析:

  MFC介面包裝類(多執行緒時成員函式呼叫的斷言失敗)
經常在論壇上看到如下的問題:

DWORD WINAPI ThreadProc( void *pData )  // 執行緒函式(比如用於從COM口獲取資料)
{
    // 資料獲取迴圈
    // 資料獲得後放在變數i中
    CAbcDialog *pDialog = reinterpret_cast< CAbcDialog* >( pData );
    ASSERT( pDialog );  // 此處如果ASSERT_VALID( pDialog )將斷言失敗
    pDialog->m_Data = i;
    pDialog->UpdateData( FALSE );  // UpdateData內部ASSERT_VALID( this )斷言失敗
   …
}
BOOL CAbcDialog::OnInitDialog()
{
    CDialog::OnInitDialog();
    // 其他初始化程式碼
    CreateThread( NULL, 0, ThreadProc, this, 0, NULL );  // 建立執行緒
    return TRUE;
}

  注意上面註釋中的兩處斷言失敗,本文從MFC底層的實現來解釋為什麼會斷言失敗,並說明MFC為什麼要這樣實現及相應的處理辦法。
  在說明MFC介面包裝類的底層實現之前,由於其和視窗有關,故先講解視窗類這個基礎知識以為後面做鋪墊。

視窗類

  視窗類是一個結構,其一個例項代表著一個視窗型別,與C++中的類的概念非常相近(雖然其表現形式完全不同,C++的類只不過是記憶體佈局和其上的操作這個概念的型別),故被稱作為視窗類。
  視窗是具有裝置操作能力的邏輯概念,即一種能操作裝置(通常是顯示器)的東西。由於視窗是視窗類的例項,就象C++中的一個類的例項,是可以具有成員函式的(雖然表現形式不同),但一定要明確視窗的目的——操作裝置(這點也可以從Microsoft針對視窗所制訂的API的功能看出,主要出於對裝置操作的方便)。因此不應因為其具有成員函式的功能而將視窗用於功能物件的建立,這雖然不錯,但是嚴重違反了語義的需要(關於語義,可參考我的另一篇文章——《語義的需要》),是不提倡的,但卻由於MFC介面包裝類的加入導致大多數程式設計師經常將邏輯混入介面。
  視窗類是個結構,其中的大部分成員都沒什麼重要意義,只是Microsoft一相情願制訂的,如果不想使用介面API(Windows User Interface API),可以不管那些成員。其中只有一個成員是重要的——lpfnWndProc,訊息處理函式。
  外界(使用視窗的程式碼)只能通過訊息操作視窗,這就如同C++中編寫的具有良好的物件導向風格的類的例項只能通過其公共成員函式對其進行操作。因此訊息處理函式就代表了一個視窗的一切(忽略視窗類中其他成員的作用)。很容易發現,視窗這個例項只具有成員函式(訊息處理函式),不具有成員變數,即沒有一塊特定記憶體和一特定的視窗相關聯,則視窗將不能具有狀態(Windows還是提供了Window Properties API來緩和這種狀況)。這也正是上面問題發生的根源。
  為了處理視窗不能具有狀態的問題(這其實正是Windows靈活的表現),可以有很多種方法,而MFC出於能夠很容易的對已有視窗類進行擴充套件,選擇了使用一個對映將一個視窗控制程式碼(視窗的唯一標示符)和一個記憶體塊進行繫結,而這塊記憶體塊就是我們熟知的MFC介面包裝類(從CWnd開始派生延續)的例項。

MFC狀態

  狀態就是例項通過某種手段使得資訊可以跨時間段重現,C++的類的例項就是由外界通過公共成員函式改變例項的成員變數的值以實現具有狀態的效果。在MFC 中,具有三種狀態:模組狀態、程式狀態、執行緒狀態。分別為模組、程式和執行緒這三種例項的狀態。由於程式碼是由執行緒執行,且和另外兩個的關係也很密切,因此也被稱作本地資料。

模組本地資料

  具有模組本地性的變數。模組指一個載入到程式虛擬記憶體空間中的PE檔案,即exe檔案本身和其載入的dll檔案。而模組本地性即同樣的指標,根據程式碼從不同的模組執行而訪問不同的記憶體空間。這其實只用每個模組都宣告一個全域性變數,而前面的“程式碼”就在MFC庫檔案中,然後通過一個切換的過程(將欲使用的模組的那個全域性變數的地址賦給前述的指標)即可實現模組本地性。MFC中,這個過程是通過呼叫AfxSetModuleState來切換的,而通常都使用 AFX_MANAGE_STATE這個巨集來處理,因此下面常見的語句就是用於模組狀態的切換的:

AFX_MANAGE_STATE( AfxGetStaticModuleState() );

  MFC中定義了一個結構(AFX_MODULE_STATE),其例項具有模組本地性,記錄了此模組的全域性應用程式物件指標、資源控制程式碼等模組級的全域性變數。其中有一個成員變數是執行緒本地資料,型別為AFX_MODULE_THREAD_STATE,其就是本文問題的關鍵。

程式本地資料

  具有程式本地性的變數。與模組本地性相同,即同一個指標,在不同程式中指向不同的記憶體空間。這一點Windows本身的虛擬記憶體空間這個機制已經實現了,不過在dll中定義的全域性變數,如果dll支援Win32s,則其是共享其全域性變數的,即不同的程式載入了同一dll將訪問同一記憶體。Win32s是為了那些基於Win32的應用程式能在Windows 3.1上執行,由於Windows 3.1是16位作業系統,早已被淘汰,而現行的dll模型其本身就已經實現了程式本地性(不過還是可以通過共享節來實現Win32s中的dll的效果),因此程式狀態其實就是一全域性變數。
  MFC中作為本地資料的結構有很多,_AFX_WIN_STATE、_AFX_DEBUG_STATE、_AFX_DB_STATE等,都是MFC內部自己使用的具有程式本地性的全域性變數。

執行緒本地資料

  具有執行緒本地性的變數。如上,即同一個指標,不同的執行緒將會訪問不同的記憶體空間。這點MFC是通過執行緒本地儲存(TLS——Thread Local Storage,其使用方法由於與本文無關,在此不表)實現的。
  MFC中定義了一個結構(_AFX_THREAD_STATE)以記錄某些執行緒級的全域性變數,如最近一次的模組狀態指標,最近一次的訊息等。

模組執行緒狀態

  MFC中定義的一個結構(AFX_MODULE_THREAD_STATE),其例項即具有執行緒本地性又具有模組本地性。也就是說不同的執行緒從同一模組中和同一執行緒從不同模組中訪問MFC庫函式都將導致操作不同的記憶體空間。其應用在AFX_MODULE_STATE中,記錄一些執行緒相關但又模組級的資料,如本文的重點——視窗控制程式碼對映。

包裝類物件和控制程式碼對映

  控制程式碼對映——CHandleMap,MFC提供的一個底層輔助類,程式設計師是不應該直接使用它的。其有兩個重要的成員變數:CMapPtrToPtr m_permanentMap, m_temporaryMap;。分別記錄永久控制程式碼繫結和臨時控制程式碼繫結。前面說過,MFC使用一個對映將視窗控制程式碼和其包裝類的例項繫結在一起,m_permanentMap和m_temporaryMap就是這個對映,分別對映永久包裝類物件和臨時包裝類物件,而在前面提到過的 AFX_MODULE_THREAD_STATE中就有一個成員變數:CHandleMap* m_pmapHWND;(之所以是CHandleMap*是使用懶惰程式設計法,儘量節約資源)以專門完成HWND的繫結對映,除此以外還有如 m_pmapHDC、m_pmapHMENU等成員變數以分別實現HDC、HMENU的綁頂對映。而為什麼這些對映要放在模組執行緒狀態而不放線上程狀態或模組狀態是很明顯的——這些包裝類包裝的控制程式碼都是和執行緒相關的(如HWND只有建立它的執行緒才能接收其訊息)且這個模組中的包裝類物件可能不同於另一個模組的(如包裝類是某個DLL中專門派生的一個類,如a.dll中定義的CAButton的例項和b.dll中定義的CBButton的例項如果同時在一個執行緒中。此時執行緒解除安裝了a.dll,然後CAButton的例項得到訊息並進行處理,將發生嚴重錯誤——類程式碼已經被解除安裝掉了)。
  包裝類存在的意義有二:包裝對HWND的操作以加速程式碼的編寫和提供視窗子類化(不是超類化)的效果以派生視窗類。包裝類物件針對執行緒分為兩種:永久包裝類物件(以後簡稱永久物件)和臨時包裝類物件(以後簡稱臨時物件)。臨時物件的意義僅僅只有包裝對HWND的操作以加速程式碼編寫,不具有派生視窗類的功能。永久物件則具有前面說的包裝類的兩個意義。
  在建立視窗時(即CWnd::CreateEx中),MFC通過鉤子提前(WM_CREATE和WM_NCCREATE之前)處理了通知,用AfxWndProc子類化了建立的視窗並將對應的CWnd*加入當前執行緒的永久物件的對映中,而在AfxWndProc中,總是由CWnd::FromHandlePermanent(獲得對應HWND的永久物件)得到當前執行緒中當前訊息所屬視窗控制程式碼對應的永久物件,然後通過呼叫得到的CWnd*的WindowProc成員函式來處理訊息以實現派生視窗類的效果。這也就是說永久物件具有視窗子類化的意義,而不僅僅是封裝HWND的操作。
  要將一個HWND和一個已有的包裝類物件相關聯,呼叫CWnd::Attach將此包裝類物件和HWND對映成永久物件(但這種方法得到的永久物件不一定具有子類化功能,很可能仍和臨時物件一樣,僅僅起封裝的目的)。如果想得到臨時物件,則通過CWnd::FromHandle這個靜態成員函式以獲得。臨時物件之所以叫臨時,就是其是由MFC內部(CHandleMap::FromHandle)生成,其內部(CHandleMap::DeleteTemp)銷燬(一般通過CWinThread::OnIdle中呼叫AfxUnlockTempMaps)。因此程式設計師是永遠不應該試圖銷燬臨時物件的(即使臨時物件所屬執行緒沒有訊息迴圈,不能呼叫CwinThread::OnIdle,線上程結束時,CHandleMap的析構仍然會銷燬臨時物件)。

原因
  為什麼要分兩種包裝類物件?很好玩嗎?注意前面提過的視窗模型——只能通過訊息機制和視窗互動。注意,也就是說視窗是執行緒安全的例項。視窗過程的編寫中不用考慮會有多個執行緒同時訪問視窗的狀態。如果不使用兩種包裝類物件,在視窗建立的鉤子中通過呼叫SetProp將建立的視窗控制程式碼和對應的CWnd*繫結,不一樣也可以實現前面說的視窗控制程式碼和記憶體塊的繫結?
  CWnd的派生類CA,具有一個成員變數m_BGColor以決定使用什麼顏色填充底背景。執行緒1建立了CA的一個例項a,將其指標傳進執行緒2,執行緒2設定a.m_BGColor為紅色。這已經很明顯了,CA::m_BGColor不是執行緒安全的,如果不止一個執行緒2,那麼a.m_BGColor將會出現執行緒訪問衝突。這嚴重違背視窗是執行緒安全的這個要求。因為使用了非訊息機制與視窗進行互動,所以失敗。
  繼續,如果給CA一個公共成員函式SetBGColor,並在其中使用原子操作以保護m_BGColor,不就一切正常了?呵,在CA::OnPaint中,會兩次使用m_BGColor進行繪圖,如果在兩次繪圖之間另一執行緒呼叫CA::SetBGColor改變了CA::m_BGColor,問題嚴重了。也就是說不光是CA::m_BGColor的寫操作需要保護,讀操作亦需要保護,而這僅僅是一個成員變數。
  那麼再繼續,完全按照視窗本身的定義,只使用訊息與它互動,也就是說自定義一個訊息,如AM_SETBGCOLOR,然後在CA::SetBGColor中SendMessage這個訊息,並在其響應函式中修改CA::m_BGColor。完美了,這是即符合視窗概念又很好的設計,不過它要求每一個程式設計師編寫每一個包裝類時都必須注意到這點,並且最重要的是,C++類的概念在這個設計中根本沒有發揮作用,嚴重地資源浪費。
  因此,MFC決定要發揮C++類的概念的優勢,讓包裝類物件看起來就等同於視窗本身,因此使用了上面的兩種包裝類物件。讓包裝類物件隨執行緒的不同而不同可以對包裝類物件進行執行緒保護,也就是說一個執行緒不可以也不應該訪問另一個執行緒中的包裝類物件(因為包裝類物件就相當於視窗,這是MFC的目標,並不是包裝類本身不能被跨執行緒訪問),“不可以”就是通過在包裝類成員函式中的斷言巨集實現的(在CWnd::AssertValid中),而“不應該”前面已經解釋地很清楚了。因此本文開頭的斷言失敗的根本原因就是因為違反了“不可以”和“不應該”。
  雖然包裝類物件不能跨執行緒訪問,但是視窗控制程式碼卻可以跨執行緒訪問。因為包裝類物件不僅等同於視窗,還改變了視窗的互動方式(這也正是C++類的概念的應用),使得不用非得使用訊息機制才能和視窗互動。注意前面提到的,如果跨執行緒訪問包裝類物件,而又使用C++類的概念操作它,則其必須進行執行緒保護,而“不能跨執行緒訪問”就消除了這個問題。因此臨時物件的產生就只是如前面所說,方便程式碼的編寫而已,不提供子類化的效果,因為視窗控制程式碼可以跨執行緒訪問。

解決辦法

  已經瞭解失敗的原因,因此做如下修改:

DWORD WINAPI ThreadProc( void *pData )  // 執行緒函式(比如用於從COM口獲取資料)
{
    // 資料獲取迴圈
    // 資料獲得後放在變數i中
    CAbcDialog *pDialog = static_cast< CAbcDialog* >(
                              CWnd::FromHandle( reinterpret_cast< HWND >( pData ) ) );
    ASSERT_VALID( pDialog );  // 此處可能斷言失敗
    pDialog->m_Data = i;      // 這是不好的設計,詳情可參看我的另一篇文章:《語義的需要》
    pDialog->UpdateData( FALSE );  // UpdateData內部ASSERT_VALID( this )可能斷言失敗
    …
}
BOOL CAbcDialog::OnInitDialog()
{
    CDialog::OnInitDialog();
    // 其他初始化程式碼
    CreateThread( NULL, 0, ThreadProc, m_hWnd, 0, NULL );  // 建立執行緒
    return TRUE;
}

  之所以是“可能”,因為這裡有個重點就是臨時物件是HWND操作的封裝,不是視窗類的封裝。因此所有的HWND臨時物件都是CWnd的例項,即使上面強行轉換為CAbcDialog*也依舊是CWnd*,所以在ASSERT_VALID裡呼叫CAbcDialog::AssertValid時,其定義了一些附加檢查,則可能發現這是一個CWnd的例項而非一個CAbcDialog例項,導致斷言失敗。因此應將CAbcDialog全部換成CWnd,這下雖然不斷言失敗了,但依舊錯誤(先不提pDialog->m_Data怎麼辦),因為臨時物件是HWND操作的封裝,而不幸的是UpdateData只是MFC自己提供的一個對話方塊資料交換的機制(DDX)的操作,其不是通過向HWND傳送訊息來實現的,而是通過虛擬函式機制。因此在UpdateData中呼叫例項的DoDataExchange將不能呼叫CAbcDialog::DoDataExchange,而是呼叫CWnd::DoDataExchange,因此將不發生任何事。
  因此合理(並不一定最好)的解決方法是向CAbcDialog的例項傳送一個訊息,而通過一箇中間變數(如一全域性變數)來傳遞資料,而不是使用CAbcDialog::m_Data。當然,如果資料少,比如本例,就應該將資料作為訊息引數進行傳遞,減少程式碼的複雜性;資料多則應該通過全域性變數傳遞,減少了緩衝的管理費用。修改後如下:

#define AM_DATANOTIFY ( WM_USER + 1 )
static DWORD g_Data = 0;
DWORD WINAPI ThreadProc( void *pData )  // 執行緒函式(比如用於從COM口獲取資料)
{
    // 資料獲取迴圈
    // 資料獲得後放在變數i中
    g_Data = i;
    CWnd *pWnd = CWnd::FromHandle( reinterpret_cast< HWND >( pData ) );
    ASSERT_VALID( pWnd );  // 本例應該直接呼叫平臺SendMessage而不呼叫包裝類的,這裡只是演示
    pWnd->SendMessage( AM_DATANOTIFY, 0, 0 );
    …
}
BEGIN_MESSAGE_MAP( CAbcDialog, CDialog )
    …
    ON_MESSAGE( AM_DATANOTIFY, OnDataNotify )
    …
END_MESSAGE_MAP()
BOOL CAbcDialog::OnInitDialog()
{
    CDialog::OnInitDialog();
    // 其他初始化程式碼
    CreateThread( NULL, 0, ThreadProc, m_hWnd, 0, NULL );  // 建立執行緒
    return TRUE;
}
LRESULT CAbcDialog::OnDataNotify( WPARAM /* wParam */, LPARAM /* lParam */ )
{
    UpdateData( FALSE );
    return 0;
}
void CAbcDialog::DoDataExchange( CDataExchange *pDX )
{
    CDialog::DoDataExchange( pDX );
    DDX_Text( pDX, IDC_EDIT1, g_Data );
}

注意事項

  “執行緒安全”是一個什麼概念?
  以前常聽高手告誡MFC物件不要跨執行緒使用,因為MFC不是執行緒安全的。比如CWnd物件不要跨執行緒使用,可以用視窗控制程式碼(HWND)代替。CSocket/CAsyncSocket物件不要跨執行緒使用,用SOCKET控制程式碼代替.那麼到底什麼是執行緒安全呢?什麼時候需要考慮?如果程式涉及到多執行緒的話,就應該考慮執行緒安全問題。比如說設計的介面,將來需要在多執行緒環境中使用,或者需要跨執行緒使用某個物件時,這個就必須考慮了。關於執行緒安全也沒什麼權威定義。在這裡我只說說我的理解:所提供的介面對於執行緒來說是原子操作或者多個執行緒之間的切換不會導致該介面的執行結果存在二義性,也就是說我們不用考慮同步的問題。
  一般而言“執行緒安全”由多執行緒對共享資源的訪問引起。如果呼叫某個介面時需要我們自己採取同步措施來保護該介面訪問的共享資源,則這樣的介面不是執行緒安全的.MFC和STL都不是執行緒安全的. 怎樣才能設計出執行緒安全的類或者介面呢?如果介面中訪問的資料都屬於私有資料,那麼這樣的介面是執行緒安全的.或者幾個介面對共享資料都是隻讀操作,那麼這樣的介面也是執行緒安全的.如果多個介面之間有共享資料,而且有讀有寫的話,如果設計者自己採取了同步措施,呼叫者不需要考慮資料同步問題,則這樣的介面是執行緒安全的,否則不是執行緒安全的。

多執行緒的程式設計應該注意些什麼呢
  儘量少的使用全域性變數、static變數做共享資料,儘量使用引數傳遞物件。被引數傳遞的物件,應該只包括必需的成員變數。所謂必需的成員變數,就是必定會被多執行緒操作的。很多人圖省事,會把this指標(可能是任意一個物件指標)當作執行緒引數傳遞,致使執行緒內部有過多的操作許可權,對this中的引數任意妄為。整個程式由一個人完成,可能會非常注意,不會出錯,但只要一轉手,程式就會面目全非。當兩個執行緒同時操作一個成員變數的時候,程式就開始崩潰了,更糟的是,這種錯誤很難被重現。(我就在鬱悶這個問題,我們是幾個人,把程式編成debug版,經過數天使用,才找到錯誤。而找到錯誤只是開始,因為你要證明這個bug被修改成功了,也非常困難。)其實,執行緒間資料互動大多是單向的,線上程回撥函式入口處,儘可能的將傳入的資料備份到區域性變數中(當然,用於執行緒間通訊的變數不能這麼處理),以後只對區域性變數做處理,可以很好的解決這種問題。
  在MFC中請慎用執行緒。因為MFC的框架假定你的訊息處理都是在主執行緒中完成的。首先視窗控制程式碼是屬於執行緒的,如果擁有視窗控制程式碼的執行緒退出了,如果另一個執行緒處理這個視窗控制程式碼,系統就會出現問題。而MFC為了避免這種情況的發生,使你在子執行緒中呼叫訊息(視窗)處理函式時,就會不停的出Assert錯誤,煩都煩死你。典型的例子就時CSocket,因為CSocket是使用了一個隱藏視窗實現了假阻塞,所以不可避免的使用了訊息處理函式,如果你在子執行緒中使用CSocket,你就可能看到assert的彈出了。
  不要在不同的執行緒中同時註冊COM元件。兩個執行緒,一個註冊1.ocx, 2.ocx, 3.ocx, 4.ocx; 而另一個則註冊5.ocx, 6.ocx, 7.ocx, 8.ocx,結果死鎖發生了,分別死在FreeLibrary和DllRegisterServer,因為這8個ocx是用MFC中做的,也可能是MFC的Bug,但DllRegisterServer卻死在GetModuleFileName裡,而GetModuleFileName則是個API唉!如果有過客看到,恰巧又知道其原因,請不吝賜教。
  不要把執行緒搞的那麼複雜。很多初學者,恨不能用上執行緒相關的所有的函式,這裡互斥,那裡等待,一會兒起執行緒,一會兒關執行緒的,比起goto語句有過之而無不及。好的多執行緒程式,應該是儘量少的使用執行緒。這句話怎麼理解吶,就是說盡量統一一塊資料共享區存放資料佇列,工作子執行緒從佇列中取資料,處理,再放回資料,這樣才會模組化,物件化;而不是每個資料都起一個工作子執行緒處理,處理完了就關閉,寫的時候雖然直接,等維護起來就累了。

後記:
在弄 gh0st 的status bar 的時候遇到了一個錯誤,正好看到了這篇文章。感覺分析很好 ,就轉載了。

相關文章