第6章 线程的基础知识
线程是由两个部分组成的:线程的内核对象和线程堆栈。
1. 创建和中止线程
线程用于描述进程中的运行路径,它包含独立的堆栈和CPU寄存器状态, 即线程的上下文(CONTEXT)。每当进程被初始化时,系统就要创建一个主线程, 该线程与C/C++运行期库的启动代码一道开始运行。对于许多应用程序来说,主线程是唯一需要的线程。不过,进程能够创建更多的线程来帮助执行它们的操作。
通常情况下,一个应用程序拥有一个用户界面线程,用于创建所有窗口,并且有一个GetMessage循环。进程中的所有其他线程都是工作线程,它们与计算机或 I/O相关联,但是这些线程从不创建窗口。用户界面线程通常拥有比工作线程更高的优先级。
如想创建一个或多个辅助函数,只需要让一个已经在运行的线程来调用CreateThread:
HANDLE CreateThread (LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程安全描述
DWORD dwStackSize, LPTHREAD_START_ROUNTINE lpStartAddress, //原型函数DWORD WINAPI ThreadFunc(LPVOID)
LPVOID lpParameter, //32bits
DWORD dwCreationFlags,
LPDWORD lpThreadId); //可返回ID
创建成功返回句柄,否则NULL. 如果dwCreationFlags==CREATE_SUSPENDED, 须等待ResumeThread以启动线程。期间可利用SetThreadPriority来设置线程优先权。
当CreateThread被调用时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。一进程内所有线程使用同一个32位空间地址,系统从进程的地址空间中分配内存,供线程的堆栈使用。新线程运行的进程环境与创建线程的环境相同。因此,新线程可以访问进程内核对象的所有句柄、进程中的所有内存和在这个相同的进程中的所有其他线程的堆栈(包括打开的文件,信号标识及动态分配的内存等)。线程的执行由系统调度程序控制。线程执行完任务后自动中止。
中止线程:
1) 线程函数返回后自动中止线程--线程函数返回(这种方法最好)。
2) 如在执行中中止,可调用
VOID ExitThread (DWORD dwExitCode); //(对象不撤消,内存堆栈撤消)
3) 如在线程外中止,可调用
BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode);
注意:ExitThread总是撤消调用的线程,而TerminateThread能够撤消任何线程,可引起系统不稳定,线程资源不释放,且TerminateThread函数是异步函数,也就是说,它告诉系统你想要线程终止运行,但当函数返回时,不能保证线程被撤消, 尽量不用。如果需要确切地知道该线程已经终止运行,必须调用WaitForSingleObject或者类似的函数,传递线程的句柄。
4) 包含线程的进程中止也可以中止线程, 尽量不要用 。
一旦线程不再运行,系统中就没有别的线程能够处理该线程的句柄。可用GetExitcodeThread来检查由hThread标识的线程是否已经终止运行。如果调用GetExitCodeThread时线程尚未终止运行,该函数就用STILL_ACTIVE标识符(定义为0x103)填入DWORD。如果该函数运行成功,便返回TRUE
2. 控制线程对共享资源的访问
在线程内,如线程完全独立,则无需考虑资源冲突问题。
如需同时访问一些资源,则必须控制:
ITC (Inter- Task Communication) 功能表
ITC功能名称 | ITC功能描述 | ITC功能使用 |
Mutex 互斥体对象 | 控制单一资源或信号的使用 | 1. 创建互斥体: HANDLE CreateMutex (); 2. 访问共享资源前,请求占用互斥体对象。WaitForSingleObject (hMutex,5000L) 3. 释放互斥体 ReleaseMutex (hMutex); |
Semaphore 信号对象 | 通过可获得信号灯数控制任务运行,允许同时对多个线程共享资源的访问。创建时指定最大同时访问线程数,当一个线程申请访问成功后,计数器减一。 | 1. 创建或打开一个信号灯 HANDLE CreateSemaphore(); HANDLE OpenSemaphore(); 2. 访问共享资源前调WaitForSingleObject 3. 释放信号占用对象ReleaseSemaphore (); |
事件对象 | 利用事件对象状态,进行线程对共享资源的访问 | SetEvent:设置事件对象状态为允许线程通过 ResetEvent:不允许线程通过 |
设置排斥区 | 在排斥区中异步执行时,只能在同进程的线程间共享资源处理. 效率高,编程简单 | 1. 定义CRITICAL_SECTION结构 2. 对象初始化 InitializeCriticalSektion() 3. 进入排斥区时,调用EnterCriticalSection或TryEnterCriticalSection函数; 退出时调用LeaveCriticalSection函数 |
互斥体对象, 信号对象和事件对象也可用于进程间的线程同步操作。
子程序继承, 通过DuplicateHandle()实现进程间线程同步操作
同进程内, 利用OpenMutex, OpenSemaphore和OpenEvent来获得指定名字的同步对象句柄。
MFC 多线程编程
基本原理同上, 不过MFC对同步对象进行了封装。
MFC线程分2种:
1. 辅助线程—用于任务处理(多为后台作业),一个基本函数代表一个线程,创建并启动线程后,线程进入运行状态。如线程用到共享资源,须进行资源同步处理。
2. 用户接口线程--处理用户输入产生的事件和消息。编写时,从CWinThread派生出自己的类.
步骤:
1) 用ClassWizard派生一个新类,设置基类为CWinThread.
注:DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏时必需的,因为创建线程时需要动态创建类的对象。根据需要可将初始化和结束代码分别放到类的InitInstance和ExitInstance函数中。如需创建窗口,则可在InitInstance函数中完成。
2) 创建线程并启动线程
2.1 利用函数CWinThread* AfxBeginThread (
CRuntimeClass* pThreadClass,
int nPriority,
UINT nStackSize,
DWORD dwCreateFlags, //创建时可先挂起,CREATE_SUSPENDED
LPSECURITY_ATTRIBUTES lpSecurityAttrs)创建用户接口程序.
挂起线程通过ResumeThread()来启动.
2.2 分两部完成:
首先调用线程类构造函数创建一个线程对象,然后调用CWinThread::CreateThread来创建该线程。
3) 使用同步对象
C++对象中使用共享资源:
线程安全类编程:根据具体情况在类中加入一个对象类数据成员。然后在类的成员函数中,凡是所有修改公共数据或者读取公共数据的地方均要加入相应的同步调用。一般为创建一个CSingleLock或者CMultiLock对象。然后调用Lock函数。结束时,Unlock。
在特定函数(线程安全函数)中使用共享资源:
建立同步对象,调用等待函数,释放对同步对象的控制
4种同步对象的使用场合类: (CSyncObject为父类)
CMutex, 用于如果有多个应用同时存取相应资源时,否则用CCriticalSection.
CSemaphore, 一个应用同时可以有多个线程存取相应资源
CEvent, 某线程必须等待某些事件发生后才能存取相应资源