MFC深刻浅出-MFC的进程和线程

2021年09月15日 阅读数:1
这篇文章主要向大家介绍MFC深刻浅出-MFC的进程和线程,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

MFC深刻浅出-MFC的进程和线程

MFC的进程和线程

 

 

  1. Win32的进程和线程概念

     

进程是一个可执行的程序,由私有虚拟地址空间、代码、数据和其余操做系统资源(如进程建立的文件、管道、同步对象等)组成。一个应用程序能够有一个或多个进程,一个进程能够有一个或多个线程,其中一个是主线程。html

线程是操做系统分时调度分配 CPU时间的基本实体。一个线程能够执行程序的任意部分的代码,即便这部分代码被另外一个线程并发地执行;一个进程的全部线程共享它的虚拟地址空间、全局变量和操做系统资源。程序员

 

之因此有线程这个概念,是由于以线程而不是进程为调度对象效率更高:安全

 

  • 因为建立新进程必须加载代码,而线程要执行的代码已经被映射到进程的地址空间,因此建立、执行线程的速度比进程更快。

     

     

  • 一个进程的全部线程共享进程的地址空间和全局变量,因此简化了线程之间的通信。

     

 

    1. Win32的进程处理简介

       

      由于 MFC没有提供类处理进程,因此直接使用了Win32 API函数。数据结构

       

       

      1. 进程的建立

         

        调用 CreateProcess函数建立新的进程,运行指定的程序。CreateProcess的原型以下:多线程

         

        BOOL CreateProcess(并发

        LPCTSTR lpApplicationName,app

        LPTSTR lpCommandLine,异步

        LPSECURITY_ATTRIBUTES lpProcessAttributes,ide

        LPSECURITY_ATTRIBUTES lpThreadAttributes,函数

        BOOL bInheritHandles,

        DWORD dwCreationFlags,

        LPVOID lpEnvironment,

        LPCTSTR lpCurrentDirectory,

        LPSTARTUPINFO lpStartupInfo,

        LPPROCESS_INFORMATION lpProcessInformation

        );

        其中:

        lpApplicationName 指向包含了要运行模块名字的字符串。

         

        lpCommandLine 指向命令行字符串。

         

        lpProcessAttributes 描述进程的安全性属性,NT下有用。

         

        lpThreadAttributes 描述进程初始线程(主线程)的安全性属性,NT下有用。

         

        bInHeritHandles 表示子进程(被建立的进程)是否能够继承父进程的句柄。能够继承的句柄有线程句柄、有名或无名管道、互斥对象、事件、信号量、映像文件、普通文件和通信端口等;还有一些句柄不能被继承,如内存句柄、DLL实例句柄、GDI句柄、URER句柄等等。

         

        子进程继承的句柄由父进程经过命令行方式或者进程间通信( IPC)方式由父进程传递给它。

         

        dwCreationFlags 表示建立进程的优先级类别和进程的类型。建立进程的类型分控制台进程、调试进程等;优先级类别用来控制进程的优先级别,分Idle、Normal、High、Real_time四个类别。

         

        lpEnviroment 指向环境变量块,环境变量能够被子进程继承。

         

        lpCurrentDirectory 指向表示当前目录的字符串,当前目录能够继承。

         

        lpStartupInfo 指向StartupInfo结构,控制进程的主窗口的出现方式。

         

        lpProcessInformation 指向PROCESS_INFORMATION结构,用来存储返回的进程信息。

         

        从其参数能够看出建立一个新的进程须要指定什么信息。

        从上面的解释能够看出,一个进程包含了不少信息。若进程建立成功的话,返回一个进程信息结构类型的指针。进程信息结构以下:

        typedef struct _PROCESS_INFORMATION {

        HANDLE hProcess;

        HANDLE hThread;

        DWORD dwProcessId;

        DWORD dwThreadId;

        }PROCESS_INFORMATION;

         

        进程信息结构包括进程句柄,主线程句柄,进程 ID,主线程ID。

         

         

      2. 进程的终止

         

进程在如下状况下终止:

 

  • 调用 ExitProcess结束进程;

     

     

  • 进程的主线程返回,隐含地调用 ExitProcess致使进程结束;

     

     

  • 进程的最后一个线程终止;

     

     

  • 调用 TerminateProcess终止进程。

     

     

  • 当要结束一个 GDI进程时,发送WM_QUIT消息给主窗口,固然也能够从它的任一线程调用ExitProcess。

     

 

    1. Win32的线程

       

       

      1. 线程的建立

         

使用 CreateThread函数建立线程,CreateThread的原型以下:

 

HANDLE CreateThread(

LPSECURITY_ATTRIBUTES lpThreadAttributes,

DWORD dwStackSize,

LPTHREAD_START_ROUTINE lpStartAddress,

LPVOID lpParameter,

DWORD dwCreationFlags, // creation flags

LPDWORD lpThreadId

);

其中:

lpThreadAttributes 表示建立线程的安全属性,NT下有用。

 

dwStackSize 指定线程栈的尺寸,若是为0则与进程主线程栈相同。

 

lpStartAddress 指定线程开始运行的地址。

 

lpParameter 表示传递给线程的32位的参数。

 

dwCreateFlages 表示是否建立后挂起线程(取值CREATE_SUSPEND),挂起后调用ResumeThread继续执行。

 

lpThreadId 用来存放返回的线程ID。

 

 

 

  • 线程的优先级别

     

进程的每一个优先级类包含了五个线程的优先级水平。在进程的优先级类肯定以后,能够改变线程的优先级水平。用 SetPriorityClass设置进程优先级类,用SetThreadPriority设置线程优先级水平。

 

Normal 级的线程能够被除了Idle级之外的任意线程抢占。

 

 

      1. 线程的终止

         

如下状况终止一个线程:

 

  • 调用了 ExitThread函数;

     

     

  • 线程函数返回:主线程返回致使 ExitProcess被调用,其余线程返回致使ExitThread被调用;

     

     

  • 调用 ExitProcess致使进程的全部线程终止;

     

     

  • 调用 TerminateThread终止一个线程;

     

     

  • 调用 TerminateProcess终止一个进程时,致使其全部线程的终止。

     

当用TerminateProcess或者TerminateThread终止进程或线程时,DLL的入口函数DllMain不会被执行(若是有DLL的话)。

 

      1. 线程局部存储

         

若是但愿每一个线程均可以有线程局部 (Thread local)的静态存储数据,可使用TLS线程局部存储技术。TLS为进程分配一个TLS索引,进程的每一个线程经过这个索引存取本身的数据变量的拷贝。

 

TLS 对DLL是很是有用的。当一个新的进程使用DLL时,在DLL入口函数DllMain中使用TlsAlloc分配TLS索引,TLS索引就做为进程私有的全局变量被保存;之后,当该进程的新的线程使用DLL时(Attahced to DLL),DllMain给它分配动态内存而且使用TlsSetValue把线程私有的数据按索引保存。DLL函数可使用TlsGetValue按索引读取调用线程的私有数据。

 

TLS 函数以下:

 

 

  • DWORD TlsAlloc()

     

在进程或 DLL初始化时调用,而且把返回值(索引值)做为全局变量保存。

 

 

  • BOOL TlsSetValue(

     

DWORD dwTlsIndex, //TLS index to set value for

LPVOID lpTlsValue //value to be stored

);

其中:

dwTlsIndex 是TlsAlloc分配的索引。

 

lpTlsValue 是线程在TLS槽中存放的数据指针,指针指向线程要保存的数据。

 

线程首先分配动态内存并保存数据到此内存中,而后调用 TlsSetValue保存内存指针到TLS槽。

 

 

  • LPVOID TlsGetValue(

     

DWORD dwTlsIndex // TLS index to retrieve value for

);

其中:

dwTlsIndex 是TlsAlloc分配的索引。

 

当要存取保存的数据时,使用索引获得数据指针。

 

  • BOOL TlsFree(

     

DWORD dwTlsIndex // TLS index to free

);

其中:

dwTlsIndex 是TlsAlloc分配的索引。

 

当每个线程都再也不使用局部存储数据时,线程释放它分配的动态内存。在 TLS索引再也不须要时,使用TlsFree释放索引。

 

 

    1. 线程同步

       

      同步能够保证在一个时间内只有一个线程对某个资源(如操做系统资源等共享资源)有控制权。共享资源包括全局变量、公共数据成员或者句柄等。同步还可使得有关联交互做用的代码按必定的顺序执行。

      Win32 提供了一组对象用来实现多线程的同步。

       

      这些对象有两种状态:得到信号 (Signaled)或者没有或则信号(Not signaled)。线程经过Win32 API提供的同步等待函数(Wait functions)来使用同步对象。一个同步对象在同步等待函数调用时被指定,调用同步函数地线程被阻塞(blocked),直到同步对象得到信号。被阻塞的线程不占用CPU时间。

       

       

      1. 同步对象

         

同步对象有: Critical_section(关键段),Event(事件),Mutex(互斥对象),Semaphores(信号量)。

 

下面,解释怎么使用这些同步对象。

 

  1. 关键段对象:

     

    首先,定义一个关键段对象 cs:

     

    CRITICAL_SECTION cs;

    而后,初始化该对象。初始化时把对象设置为 NOT_SINGALED,表示容许线程使用资源:

     

    InitializeCriticalSection(&cs);

    若是一段程序代码须要对某个资源进行同步保护,则这是一段关键段代码。在进入该关键段代码前调用 EnterCriticalSection函数,这样,其余线程都不能执行该段代码,若它们试图执行就会被阻塞。

     

    完成关键段的执行以后,调用 LeaveCriticalSection函数,其余的线程就能够继续执行该段代码。若是该函数不被调用,则其余线程将无限期的等待。

     

     

  2. 事件对象

     

    首先,调用 CreateEvent函数建立一个事件对象,该函数返回一个事件句柄。而后,能够设置(SetEvent)或者复位(ResetEvent)一个事件对象,也能够发一个事件脉冲(PlusEvent),即设置一个事件对象,而后复位它。复位有两种形式:自动复位和人工复位。在建立事件对象时指定复位形式。。

     

    自动复位:当对象得到信号后,就释放下一个可用线程(优先级别最高的线程;若是优先级别相同,则等待队列中的第一个线程被释放)。

    人工复位:当对象得到信号后,就释放全部可利用线程。

    最后,使用 CloseHandle销毁建立的事件对象。

     

     

  3. 互斥对象

     

    首先,调用 CreateMutex建立互斥对象;而后,调用等待函数,能够的话利用关键资源;最后,调用RealseMutex释放互斥对象。

     

    互斥对象能够在进程间使用,但关键段对象只能用于同一进程的线程之间。

     

  4. 信号量对象

     

    在 Win32中,信号量的数值变为0时给以信号。在有多个资源须要管理时可使用信号量对象。

     

    首先,调用 CreateSemaphore建立一个信号量;而后,调用等待函数,若是容许的话,则利用关键资源;最后,调用RealeaseSemaphore释放信号量对象。

     

     

  5. 此外,还有其余句柄能够用来同步线程:

     

文件句柄(FILE HANDLES)

命名管道句柄(NAMED PIPE HANDELS)

控制台输入缓冲区句柄(CONSOLE INPUT BUFFER HANDLES)

通信设备句柄(COMMUNICTION DEVICE HANDLES)

进程句柄(PROCESS HANDLES)

线程句柄(THREAD HANDLES)

例如,当一个进程或线程结束时,进程或线程句柄得到信号,等待该进程或者线程结束的线程被释放。

 

      1. 等待函数

         

Win32 提供了一组等待函数用来让一个线程阻塞本身的执行。等待函数分三类:

 

 

  1. 等待单个对象的 (FOR SINGLE OBJECT):

     

    这类函数包括:

    SignalObjectAndWait

    WaitForSingleObject

    WaitForSingleObjectEx

    函数参数包括同步对象的句柄和等待时间等。

    在如下状况下等待函数返回:

    同步对象得到信号时返回;

    等待时间达到了返回:若是等待时间不限制 (Infinite),则只有同步对象得到信号才返回;若是等待时间为0,则在测试了同步对象的状态以后立刻返回。

     

     

  2. 等待多个对象的 (FOR MULTIPLE OBJECTS)

     

    这类函数包括:

    WaitForMultipleObjects

    WaitForMultipleObjectsEx

    MsgWaitForMultipleObjects

    MsgWaitForMultipleObjectsEx

    函数参数包括同步对象的句柄,等待时间,是等待一个仍是多个同步对象等等。

    在如下状况下等待函数返回:

    一个或所有同步对象得到信号时返回(在参数中指定是等待一个或多个同步对象);

    等待时间达到了返回:若是等待时间不限制 (Infinite),则只有同步对象得到信号才返回;若是等待时间为0,则在测试了同步对象的状态以后立刻返回。

     

     

  3. 能够发出提示的函数 (ALTERABLE)

     

这类函数包括:

MsgWaitForMultipleObjectsEx

SignalObjectAndWait

WaitForMultipleObjectsEx

WaitForSingleObjectEx

这些函数主要用于重叠 (Overlapped)的I/O(异步I/O)。

 

 

    1. MFC的线程处理

       

      在 Win32 API的基础之上,MFC提供了处理线程的类和函数。处理线程的类是CWinThread,函数是AfxBeginThread、AfxEndThread等。

       

      表 5-6解释了CWinThread的成员变量和函数。

       

      CWinThread 是MFC线程类,它的成员变量m_hThread和m_hThreadID是对应的Win32线程句柄和线程ID。

       

      MFC 明确区分两种线程:用户界面线程(User interface thread)和工做者线程(Worker thread)。用户界面线程通常用于处理用户输入并对用户产生的事件和消息做出应答。工做者线程用于完成不要求用户输入的任务,如耗时计算。

       

      Win32 API 并不区分线程类型,它只须要知道线程的开始地址以便它开始执行线程。MFC为用户界面线程特别地提供消息泵来处理用户界面的事件。CWinApp对象是用户界面线程对象的一个例子,CWinApp从类CWinThread派生并处理用户产生的事件和消息。

       

       

      1. 建立用户界面线程

         

经过如下步骤建立一个用户界面线程:

 

  • 从 CWinThread派生一个有动态建立能力的类。使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏来支持动态建立。

     

     

  • 覆盖 CWinThread的一些虚拟函数,能够覆盖的函数见表5-4关于CWinThread的部分。其中,函数InitInstance是必须覆盖的,ExitInstance一般是要覆盖的。

     

     

  • 使用 AfxBeginThread建立MFC线程对象和Win32线程对象。若是建立线程时没有指定CREATE_SUSPENDED,则开始执行线程。

     

     

  • 若是建立线程是指定了 CREATE_SUSPENDED,则在适当的地方调用函数ResumeThread开始执行线程。

     

 

      1. 建立工做者线程

         

        程序员没必要从 CWinThread派生新的线程类,只须要提供一个控制函数,由线程启动后执行该函数。

         

        而后,使用 AfxBeginThread建立MFC线程对象和Win32线程对象。若是建立线程时没有指定CREATE_SUSPENDED(建立后挂起),则建立的新线程开始执行。

         

        若是建立线程是指定了 CREATE_SUSPENDED,则在适当的地方调用函数ResumeThread开始执行线程。

         

        虽然程序员没有从 CWinThread派生类,可是MFC给工做者线程提供了缺省的CWinThread对象。

         

         

      2. AfxBeginThread

         

用户界面线程和工做者线程都是由 AfxBeginThread建立的。如今,考察该函数:MFC提供了两个重载版的AfxBeginThread,一个用于用户界面线程,另外一个用于工做者线程,分别有以下的原型和过程:

 

 

  1. 用户界面线程的 AfxBeginThread

     

    用户界面线程的 AfxBeginThread的原型以下:

     

    CWinThread* AFXAPI AfxBeginThread(

    CRuntimeClass* pThreadClass,

    int nPriority,

    UINT nStackSize,

    DWORD dwCreateFlags,

    LPSECURITY_ATTRIBUTES lpSecurityAttrs)

    其中:

    参数 1是从CWinThread派生的RUNTIME_CLASS类;

     

    参数 2指定线程优先级,若是为0,则与建立该线程的线程相同;

     

    参数 3指定线程的堆栈大小,若是为0,则与建立该线程的线程相同;

     

    参数 4是一个建立标识,若是是CREATE_SUSPENDED,则在悬挂状态建立线程,在线程建立后线程挂起,不然线程在建立后开始线程的执行。

     

    参数 5表示线程的安全属性,NT下有用。

     

     

  2. 工做者线程的 AfxBeginThread

     

    工做者线程的 AfxBeginThread的原型以下:

     

    CWinThread* AFXAPI AfxBeginThread(

    AFX_THREADPROC pfnThreadProc,

    LPVOID pParam,

    int nPriority,

    UINT nStackSize,

    DWORD dwCreateFlags,

    LPSECURITY_ATTRIBUTES lpSecurityAttrs)

    其中:

    参数 1指定控制函数的地址;

     

    参数 2指定传递给控制函数的参数;

     

    参数 三、四、5分别指定线程的优先级、堆栈大小、建立标识、安全属性,含义同用户界面线程。

     

     

  3. AfxBeginThread 建立线程的流程

     

不论哪一个AfxBeginThread,首先都是建立MFC线程对象,而后建立Win32线程对象。在建立MFC线程对象时,用户界面线程和工做者线程的建立分别调用了不一样的构造函数。用户界面线程是从CWinThread派生的,因此,要先调用派生类的缺省构造函数,而后调用CWinThread的缺省构造函数。图8-1中两个构造函数所调用的CommonConstruct是MFC内部使用的成员函数。

MFC深刻浅出-MFC的进程和线程_MFC

 

      1. CreateThread和_AfxThreadEntry

         

        MFC 使用CWinThread::CreateThread建立线程,不论对工做者线程或用户界面线程,都指定线程的入口函数是_AfxThreadEntry。_AfxThreadEntry调用AfxInitThread初始化线程。

         

        CreateThread 和_AfxThreadEntry在线程的建立过程当中使用同步手段交互等待、执行。CreateThread由建立线程执行,_AfxThreadEntry由被建立的线程执行,二者经过两个事件对象(hEvent和hEvent2)同步:

         

        在建立了新线程以后,建立线程将在 hEvent事件上无限等待直到新线程给出建立结果;新线程在建立成功或者失败以后,触发事件hEvent让父线程运行,而且在hEven2上无限等待直到父线程退出CreateThread函数;父线程(建立线程)由于hEvent的置位结束等待,继续执行,退出CreateThread以前触发hEvent2事件;新线程(子线程)由于hEvent2的置位结束等待,开始执行控制函数(工做者线程)或者进入消息循环(用户界面线程)。

         

        MFC 在线程建立中使用了以下数据结构:

         

        struct _AFX_THREAD_STARTUP

        {

        // 传递给线程启动的参数(IN)

         

        _AFX_THREAD_STATE* pThreadState;// 父线程的线程状态

         

        CWinThread* pThread; // 新建立的MFC线程对象

         

        DWORD dwCreateFlags; // 线程建立标识

         

        _PNH pfnNewHandler; // 新线程的句柄

         

        HANDLE hEvent; // 同步事件,线程建立成功或失败后置位

         

        HANDLE hEvent2; // 同步事件,新线程恢复执行后置位

         

         

        // 返回给建立线程的参数,在新线程恢复执行后赋值

         

        BOOL bError; // 若是建立发生错误,TRUE

         

        };

        该结构做为线程开始函数的参数被传递给 _beginthreadex函数来建立和启动线程。_beginthreadex函数是“C”的线程建立函数,具备以下原型:

         

        unsigned long _beginthreadex(

        void *security,

        unsigned stack_size,

        unsigned ( __stdcall *start_address )( void * ),

        void *arglist,

        unsigned initflag,

        unsigned *thrdaddr );

         

        图 8-2描述了上述过程。图中表示,_AfxThreadEntry在启动线程时,将建立本线程的线程状态,而且继承父线程的模块状态。关于MFC状态,见第9章。

         

         

        MFC深刻浅出-MFC的进程和线程_MFC_02

         

         

        MFC深刻浅出-MFC的进程和线程_MFC_03

         

         

      2. 线程的结束

         

        从图 8-2能够看出,AfxEndThread用来结束调用它的线程:它将清理本线程建立的MFC对象和释放线程局部存储分配的内存空间;调用CWinThread的虚拟函数Delete;调用“C”的结束线程函数_endthreadex释放分配给线程的资源,可是不关闭线程句柄。

         

        CWinThread::Delete 的缺省实现是:若是本线程的成员函数m_bDelete为TRUE,则调用“C”运算符号delete销毁MFC线程对象自身(delete this),这将致使线程对象的析构函数被调用。若析构函数检测线程句柄非空则调用CloseHandle关闭它。

         

        一般,让 m_bDelete为TRUE以便自动地销毁线程对象,释放内存空间(MFC内存对象在堆中分配)。可是,有时候,在线程结束以后(Win32线程已经不存在)保留MFC线程对象是有用的,固然程序员本身最后要记得销毁该线程对象。

         

         

      3. 实现线程的消息循环

         

在 MFC中,消息循环是由线程完成的。通常地,可使用MFC缺省的消息循环(即便用函数CWindThrad::Run),可是,有些时候须要程序员本身实现一个线程的消息循环,好比在用户界面线程进行一个长时间计算处理或者等待另外一个线程时。通常有以下形式:

 

while ( bDoingBackgroundProcessing)

{

MSG msg;

while ( ::PeekMessage( &msg, NULL,0, 0, PM_NOREMOVE ) )

{

if ( !PumpMessage( ) )

{

bDoingBackgroundProcessing = FALSE;

::PostQuitMessage( );

break;

}

}

// let MFC do its idle processing

LONG lIdle = 0;

while ( AfxGetApp()->OnIdle(lIdle++ ) );

// Perform some background processing here

// using another call to OnIdle

}

该段代码的解释参见图 5-3对线程的Run函数的图解。

 

 

程序员实现线程的消息循环有两个好处,一是顾及了 MFC的Idle处理机制;二是在长时间的处理中能够响应用户产生的事件或者消息。

 

在同步对象上等待其余线程时,也可使用一样的方式,只要把条件

bDoingBackgroundProcessing

换成以下形式:

WaitForSingObject(hHandleOfEvent,0) == WAIT_TIMEOUT

便可。

MFC 处理线程和进程时还引入了一个重要的概念:状态,如线程状态(Thread State)、进程状态(Process State)、模块状态(Module State)等。因为这个概念在MFC中占有重要地位,涉及的内容比较多,因此专门在下一章来说述它。