MFC多线程技术

天涯浪子 提交于 2019-12-26 23:38:20

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;
}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!