MFC多線程技術


MFC中有兩類線程,分別稱之為工作者線程和用戶界面線程。二者的主要區別在於工作者線程沒有消息循環,而用戶界面線程有自己的消息隊列和消息循環。

工作者線程沒笑消息機制,通常用來執行后台計算和維護任務,如冗長的計算過程,打印機的后台打印等。用戶界面線程一般用於處理獨立於其他線程之外的用戶輸入,響應用戶及系統產生的事件和消息等。但對於Win32的API編程而言,這兩種編程是沒有區別的,他們都只需要線程的啟動地址即可啟動線程來執行任務。

在MFC中,一般用全局函數AfxBeginThread()來創建並初始化一個線程的運行,該函數有兩種重載形式,分別用於創建工作者線程和用戶界面線程。這兩種函數的重載和原型分別說明如下:

(1)工作者線程

CWndThread *AfxBeginThread(AFX_THREADPROC pfnThreadProc,
    LPVOID pParam,
    UINT nPriority=THREAD_PRIORITY_NORMAL,
    UINT nStackSize = 0,
    DWORD dwCreateFlags = 0,
    LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);

(2)IU線程(用戶界面線程)

CWndThread *AfxBeginThread(CRuntimeClass *pThreadClass,
    int nPriority=THREAD_PRIORITY_NORMAL,
    UINT nStackSize = 0,
    DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);

AfxBeginThread()創建線程的流程不論哪個AfxBeginThread(),首先都是創建MFC線程對象,然后創建Win32線程對象。

 

AfxBeginThread創建線程的流程圖

 

 

MFC線程技術剖析

MFC的核心類庫中有一個名為CWinThread的類,這個類在MFC的底層機理中占舉足輕重的地位。

 

                           MFC應用程序     

 

線程狀態用類_AFX_THREAD_STATE描述,模塊狀態用類_AFX_MODULE_STATE描述,模塊-線程狀態用類_AFX_MODULE_THREAD_STATE描述。這些類從類CNoTrackObject派生。進程狀態用類_AFX_BASE_MODULE_STATE描述,從模塊狀態_AFX_MODULE_STATE派生。進程狀態是一個可以獨立執行的MFC應用程序的模塊狀態。還有其他狀態如DLL的模塊狀態等也從模塊狀態類_AFX_MODULE_STATE派生。

MFC狀態類的層次

 

模塊、線程、模塊-線程狀態的關系

 

多線程實踐案例:(多線程文件查找器)

查找文件的時候,首先用FindFirstFile函數,如果函數執行成功,返回句柄hFindFile來對應這個尋找操作,接下來可以利用這個句柄循環調用FindNextFile函數繼續查找其他文件,知道該函數返回失敗(FALSE)為止。最后還要調用FindClose函數關閉hFindFile句柄。

hFindFile = ::FindFirstFile(lpFileName,lpFindData);

if(hFindFile != INVALID_HANDLE_VALUE)
{
    do // 處理本次找到的文件
    {

}while(::FindNextFile(lpFileName,lpFindData));
::FindColse(hFindFile);
}

文件搜索器要在指定的目錄及所有子層目錄中查找文件,然后向用戶顯示出查找的結果。如果使用多線程的話,就意味着各線程要同時在不同目錄中搜索文件。

這個程序最關鍵的地方是定義了一個動態的目錄列表。

CTypedSimpleList<CDirectoryNode *> m_listDir;
struct CDirectoryNode : public CNoTrackObject
{
    CDirectoryNode* pNext; // CTypedSimpleList類模板要用次成員
    char szDir[MAX_PATH];  // 要查找的目錄
}

在線程執行查找文件任務的時候,如果找到的是目錄就將它添加到列表中,若找到的是文件,就用自定義CheckFile函數進行比較,判斷是否符合查找條件,若符合就打印出來,顯示給用戶。線程在查找完一個目錄以后,再從m_listDir列表中取出一個新的目錄進行查找,同時將該目錄對應的結點從表中刪除。

當m_listDir為空時,線程就要進入暫停狀態,等待其他線程向m_listDir中添加新的目錄。

案例:

RapidFile.h文件

#pragma once
#include <afxwin.h>

struct CDirectoryNode : public CNoTrackObject  // 創建文件夾目錄結構體
{
    CDirectoryNode *pNext;  // 文件夾目錄的下一個指針
    char szDir[MAX_PATH];   // 文件夾名稱
}

class CRapidFinder
{
public:
    CRapidFinder(int nMaxThread);      // 構造函數
    virtual ~CRapidFinder();        // 虛析構函數
    
    BOOL CheckFile(LPCTSTR lpszFileName);  // 匹配文件夾名字
    
    int m_nResultCount; // 結果的數量
    int m_nThreadCount;  // 活動線程的數量
    
    CTypeSimpleList<CDirectoryNode *> m_listDir;  // 文件夾列表
    CRITICAL_SECTION m_cs; // 臨界區
    
    const int m_nMaxThread;  // 最大線程數量
    char m_szMatchName[MAX_PATH]; // 最大搜索的文件
    
    // 通知線程的工作狀態
    HANDLE m_hDirEvent; //我們向m_listDir添加新的目錄,10個線程 9個停止,1個工作 若m_listDir為空,線程不能停止
    HANDLE m_hExitEvent; // 各個搜索線程是否已經結束
}

RapidFile.cpp文件

#include "RapidFile"
#include <string>

CRapidFinder::CRapidFinder(int nMaxThread) : m_nMaxThread(nMaxThread)
{
    m_nResultCount = 0;
    m_nThreadCount = 0;
    m_szMatchName[0] = '\0';
    
    m_listDir.Construct(offsetof(CDirectoryNode,pNext)); // 創建CTypedSimpleList
    m_hDirEvent = ::CreateEvent(NULL,FALSE,FALSE,NULL);
    m_hExitEvent = ::CreateEvent(NULL,FALSE,FALSE,NULL);
    ::InitializeCriticalSectioin(&m_cs);
}

CRapidFinder::~CRapidFinder()
{
    ::CloseHandle(m_hDirEvent);
    ::CloseHandle(m_hExitEvent);
    ::DeleteCriticalSection(&m_cs);
}

// 查找文件名
BOOL CRapidFinder::CheckFile(LPCTSTR lpszFileName)
{
    char str[MAX_PATH];
    char strSearch[MAX_PATH];
    strcpy(str,lpszFileName);
    strcpy(strSearch,m_szMatchName);
    
    _strupr(str); // 將字符串全部轉換為大寫
    _strupr(strSearch);
    
    if(strstr(str,strSearch) != NULL)  // 查找的文件名在里面
    {
        return TRUE;
    }
    return FALSE;
}

MultiThreadFindFile.cpp

#include <stdio.h>
#include <afxwin.h>
#include "RapidFile.h"

UINT FinderEntry(LPVOID lpParam)
{
    CRapidFinder *pFinder = (CRapidFinder *)lpParam;
    CDirectoryNode *pNode = NULL;  // m_listDir從pNode中獲取
    BOOL bActive = TRUE; // 線程狀態
    
    // 只要m_listDir有目錄
    while(1)
    {
        // 取出新目錄 互斥的取待查目錄
        ::EnterCriticalSection(&pFinder->m_cs);
        if(pFinder->m_listDir.IsEmpty())
            bActive = FALSE;
        else
        {
            pNode = pFinder->m_listDir.GetHead();
            pFinder->m_listDir.Remove(pNode);
        }
        ::LeaveCriticalSection(&pFinder->m_cs);
        
        // bActive指示了當前線程的工作狀態,如果m_listDir隊列當前為空,那么我們當前線程先等待
        if(!bActive)
        {
            ::EnterCriticalSection(&pFinder->m_cs);
            pFinder->m_nThreadCount--; 
            if(pFinder->m_nThreadCount == 0)
            {
                ::LeaveCriticalSection(&pFinder->m_cs);
                break;
            }
            ::LeaveCriticalSection(&pFinder->m_cs);
            
            // 進入等待狀態
            ResetEvent(pFinder>m_hDirEvent);
            ::WaitForSingleObject(pFinder->m_hDirEvent,INFINITE);
            
            ::EnterCriticalSection(&pFinder->m_cs);
            // 此時當前線程再度獲得CPU的推進機會
            pFinder->m_nThreadCount++; // 當前的活動線程數量加1
            ::LeaveCriticalSection(&pFinder->m_cs);
            
            bActive = TRUE;
            continue;
        }
        
        // 實現基於pNode的目錄查找
        WIN32_FIND_DATA fileData;
        HANDLE hFindFile;
        if(pNode->szDir[strlen(pNode->szDir)-1] != '\')
            strcat(pNode->szDir,'\\');
        strcat(pNode->szDir,"*.*");
        hFindFile = ::FindFirstFile(pNode->szDir,&fileData);
        
        if(hFindFile != INVALID_HANDLE_VALUE)
        {
            do
            {
                if(fileData.cFileName[0] == '.')
                    continue;
                if(fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                { // 是目錄,添加到m_listDir
                    CDirectoryNode *p = new CDirectoryNode;
                    strncpy(p->szDir,pNode->szDir,strlen(pNode->szDir)-3);
                    strcat(p->szDir,fileData.cFileName);
                    
                    ::EnterCriticalSection(&pFinder->m_cs);
                    pFinder->m_listDir.AddHead(p);
                    ::LeaveCriticalSection(&pFinder->m_cs);
                    // 置信一個事件
                    ::SetEvent(pFinder->m_hDirEvent);
                }
                else // 文件
                {
                    if(pFinder->CheckFile(fileData.cFileName)) // 找到文件名
                    {
                        ::EnterCriticalSection(&pFinder->m_cs);
                        ::InterlockedIncrement((long *)&pFinder->m_nResultCount);
                        ::LeaveCriticalSection(&pFinder->m_cs);
                        printf("%s \n",fileData.cFileName);
                    }
                }
            }while(::FindNextFile(pNode->szDir,&fileData));
        }
        // 此節點的保存的目錄已經全部搜索完畢
        delete pNode;
        pNode = NULL;
    }
    ::SetEvent(pFinder->m_hExitEvent);
    // 判斷當前線程是否是最后一個結束循環的線程
    if(::WaitForSingleObject(pFinder->m_hDirEvent,0) != WAIT_TIMEOUT)
    { // 通知主線程,最后一個搜索線程已經結束了
        ::SetEvent(pFinder->m_hExitEvent);
    }
    
    return 0;
}

int main(void)
{
    CRapidFinder *pFinder = new CRapidFinder(64); // 開64個線程
    CDirectoryNode *pNode = new CDirectoryNode; // 創建結點
    
    char szPath[] = "C:\\";  // 需要查找的目錄
    char szFile[] = "stdafx";    // 需要查找的字符串
    
    // 對CRapider的信息進行設置
    strcpy(pNode->szDir,szPath);    // 設置要搜索的目錄
    pFinder->m_listDir.AddHead(pNode); // 將要搜索的目錄添加到list中,當做頭結點
    strcpy(pFinder->m_szMatchName,szFile); // 需要搜索的文件名
    
    // 創建輔助線程
    pFinder->m_nThreadCount = pFinder->m_nMaxThread;
    
    // 創建輔助線程,並等待查找結束
    for(int i =0; i < pFinder->m_nMaxThread; i++)
    {
        AfxBeginThread(FinderEntry,pFinder);
    }
    WaitForSingleObject(pFinder->m_hExitEvent,INFINITE);
    // 打印查找結果
    printf("一共找到同名文件%d個\n");
    
    delete pFinder;
    
    system("pause");
    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM