Windwos 多线程程序设计 多线程概述 Win32 API对多线程编程的支持 线程之间的同步 哲学家进餐问题分析与实现
1、多线程概述(1) 进程和线程都是操作系统的概念。进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,进程在运行过程中创建的资源随着进程的终止而被销毁,所使用的系统资源在进程终止时被释放或关闭。 线程是进程内部的一个执行单元。系统创建好进程后,实际上就启动执行了该进程的主执行线程,主执行线程以函数地址形式,比如说main或WinMain函数,将程序的启动点提供给Windows系统。主执行线程终止了,进程也就随之终止。
多线程概述(2) 每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源,所以线程间的通讯非常方便,多线程技术的应用也较为广泛。 多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。要说明的一点是,目前大多数的计算机都是单处理器(CPU)的,为了运行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮换方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。由此可见,如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,反而会降低系统的性能。这一点在多线程编程时应该注意。
多线程概述(3) Win32 SDK函数支持进行多线程的程序设计,并提供了操作系统原理中的各种同步、互斥和临界区等操作。Visual C++ 6.0中,使用MFC类库也实现了多线程的程序设计,使得多线程编程更加方便。
2、Win32 API对多线程编程的支持 CreateThread SuspendThread ResumeThread和 ExitThread GetExitCodeThread TerminateThread WaitForSingleObject WaitForMultipleObjects
CreateThread的原型 HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);
CreateThread的参数 该函数在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄 HANDLE LPSECURITY_ATTRIBUTES lpThreadAttributes:指向一个 SECURITY_ATTRIBUTES 结构的指针,该结构决定了线程的安全属性,一般置为 NULL; DWORD dwStackSize:指定了线程的堆栈深度,一般都设置为0; LPTHREAD_START_ROUTINE lpStartAddress :表示新线程开始执行时代码所在函数的地址,即线程的起始地址。一般情况为(LPTHREAD_START_ROUTINE)ThreadFunc,ThreadFunc 是线程函数名; LPVOID lpParameter :指定了线程执行时传送给线程的32位参数,即线程函数的参数; DWORD dwCreationFlags :控制线程创建的附加标志,可以取两种值。如果该参数为0,线程在被创建后就会立即开始执行;如果该参数为CREATE_SUSPENDED,则系统产生线程后,该线程处于挂起状态,并不马上执行,直至函数ResumeThread被调用; LPDWORD lpThreadId lpThreadId:该参数返回所创建线程的ID; 如果创建成功则返回线程的句柄,否则返回NULL。
SuspendThread DWORD SuspendThread(HANDLE hThread); 该函数用于挂起指定的线程,如果函数执行成功,则线程的执行被挂起。
ResumeThread和 ExitThread DWORD ResumeThread(HANDLE hThread);该函数用于结束线程的挂起状态,执行线程。 VOID ExitThread(DWORD dwExitCode); 该函数用于线程终结自身的执行,主要在线程的执行函数中被调用。其中参数dwExitCode用来设置线程的退出码。
GetExitCodeThread 功能: 获取一个结束线程的返回值 原形: BOOL GetExitCodeThread( HANDLE hThread, LPDWORD lpExitCode); 参数:hThread 指向欲获取返回值的线程对象的句柄 lpExitCode 用于存储线程的返回值 STILL_ACTIVE 表示线程还在运行,其他值表示线程的返回值 返回值:函数执行成功则返回非0值,否则返回 0(FALSE)
TerminateThread BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);
WaitForSingleObject DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds); hHandle为要监视的对象(一般为同步对象,也可以是线程)的句柄; dwMilliseconds为hHandle对象所设置的超时值,单位为毫秒; 当在某一线程中调用该函数时,线程暂时挂起,系统监视hHandle所指向的对象的状态。如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。
WaitForMultipleObjects DWORD WaitForMultipleObjects( DWORD nCount, // 等待的对象数量 CONST HANDLE *lpHandles, // 对象句柄数组指针 BOOL fWaitAll, // 等待方式,为TRUE表示等待全部对象都变为有信号状态才返回,为FALSE表示任何一个对象变为有信号状态则返回 DWORD dwMilliseconds // 超时设置,以ms为单位,如果为INFINITE表示无限期的等待 ); 返回值意义: WAIT_OBJECT_0 到 (WAIT_OBJECT_0 + nCount – 1):当fWaitAll为TRUE时表示所有对象变为有信号状态,当fWaitAll为FALSE时使用返回值减去WAIT_OBJECT_0得到变为有信号状态的对象在数组中的下标。 WAIT_ABANDONED_0 到 (WAIT_ABANDONED_0 + nCount – 1):表示对象中有一个对象为互斥量,该互斥量因为被关闭而成为有信号状态,使用返回值减去WAIT_ABANDONED_0得到变为有信号状态的对象在数组中的下标。 WAIT_TIMEOUT:表示超过规定时间。
线程之间的同步 各个线程可以访问进程中的公共变量,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。前面一节介绍的事件对象实际上就是一种同步形式。 临界区(critical section) 互斥体(Mutexes) 信号量(Semaphore) 事件(Event)
临界区(CRITICAL_SECTION) 临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中,需要给各个线程提供一个共享的临界区对象,无论哪个线程占有临界区对象,都可以访问受到保护的数据,这时候其它的线程需要等待,直到该线程释放临界区对象为止,临界区被释放后,另外的线程可以强占这个临界区,以便访问共享的数据。用临界区对象:
临界区的操作函数 VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection );产生临界区 VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection );删除临界区 VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection );进入临界区,相当于申请加锁,如果该临界区正被其他线程使用则该函数会等待到其他线程释放 BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection );进入临界区,相当于申请加锁,和EnterCriticalSection不同如果该临界区正被其他线程使用则该函数会立即返回FALSE,而不会等待 VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection );退出临界区,相当于申请解锁
互斥体(MUTEXS) 互斥体与临界区很相似,但是使用时相对复杂一些,它不仅可以在同一应用程序的线程间实现同步,还可以在不同的进程间实现同步,从而实现资源的安全共享。
互斥量的操作函数(建立与打开) 创建互斥量: 打开一个存在的互斥量: HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes,// 安全信息 ,NULL表示默认属性 BOOL bInitialOwner, // 最初状态,如果设置为真,则表示创建它的线程直接拥有了该互斥量,而不需要再申请 LPCTSTR lpName // 名字,可以为NULL,但这样一来就不能被其他进程打开 ); 打开一个存在的互斥量: HANDLE OpenMutex( DWORD dwDesiredAccess, // 存取方式 BOOL bInheritHandle, // 是否可以被继承 LPCTSTR lpName // 名字 );
互斥量的操作函数(释放与关闭) 释放互斥量的使用权,但要求调用该函数的线程拥有该互斥量的使用权: 关闭互斥量: BOOL ReleaseMutex(HANDLE hMutex // 句柄 ); 关闭互斥量: BOOL CloseHandle( HANDLE hObject // 句柄 );
获取互斥量的使用权 (1) DWORD WaitForSingleObject( HANDLE hHandle, // 等待的对象的句柄 DWORD dwMilliseconds // 等待的时间,以ms为单位,如果为INFINITE表示无限期的等待 ); 返回: WAIT_ABANDONED 在等待的对象为互斥量时表明因为互斥量被关闭而变为有信号状态 WAIT_OBJECT_0 得到使用权 WAIT_TIMEOUT 超过(dwMilliseconds)规定时间
获取互斥量的使用权 (2) DWORD WaitForMultipleObjects( DWORD nCount, // 等待的对象数量 CONST HANDLE *lpHandles, // 对象句柄数组指针 BOOL fWaitAll, // 等待方式,为TRUE表示等待全部对象都变为有信号状态才返回,为FALSE表示任何一个对象变为有信号状态则返回 DWORD dwMilliseconds // 超时设置,以ms为单位,如果为INFINITE表示无限期的等待 ); 返回值意义: WAIT_OBJECT_0 到 (WAIT_OBJECT_0 + nCount – 1):当fWaitAll为TRUE时表示所有对象变为有信号状态,当fWaitAll为FALSE时使用返回值减去WAIT_OBJECT_0得到变为有信号状态的对象在数组中的下标。 WAIT_ABANDONED_0 到 (WAIT_ABANDONED_0 + nCount – 1):表示对象中有一个对象为互斥量,该互斥量因为被关闭而成为有信号状态,使用返回值减去WAIT_ABANDONED_0得到变为有信号状态的对象在数组中的下标。 WAIT_TIMEOUT:表示超过规定时间。
信号量(Semaphore ) 信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同一个资源。
信号量操作函数(1) 创建信号灯: HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,// 安全属性,NULL表示使用默认的安全描述 LONG lInitialCount, // 初始值 LONG lMaximumCount, // 最大值 LPCTSTR lpName // 名字 ); 打开信号灯: HANDLE OpenSemaphore( DWORD dwDesiredAccess, // 存取方式 BOOL bInheritHandle, // 是否能被继承
信号量操作函数(2) 释放信号灯: BOOL ReleaseSemaphore( HANDLE hSemaphore, // 句柄 LONG lReleaseCount, // 释放数,让信号灯值增加数 LPLONG lpPreviousCount // 用来得到释放前信号灯的值,可以为NULL ); 关闭信号灯: BOOL CloseHandle( HANDLE hObject // 句柄 );
事件 事件,前面讲的信号灯和互斥量可以保证资源被正常的分配和使用,而事件是用来通知其他进程/线程某件操作已经完成。 事件对象可以一两种方式创建,一种为自动重置,在其他线程使用WaitForSingleObject等待到事件对象变为有信号后该事件对象自动又变为无信号状态,一种为人工重置在其他线程使用WaitForSingleObject等待到事件对象变为有信号后该事件对象状态不变。例如有多个线程都在等待一个线程运行结束,我们就可以使用人工重置事件,在被等待的线程结束时设置该事件为有信号状态,这样其他的多个线程对该事件的等待都会成功(因为该事件的状态不会被自动重置)。
事件对象操作函数(1) 创建事件对象: HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes,// 安全属性,NULL表示使用默认的安全描述 BOOL bManualReset, // 是否为人工重置 BOOL bInitialState, // 初始状态是否为有信号状态 LPCTSTR lpName // 名字 ); 打开事件对象: HANDLE OpenEvent( DWORD dwDesiredAccess, // 存取方式 BOOL bInheritHandle, // 是否能够被继承
事件对象操作函数(2) 设置事件为无信号状态: BOOL ResetEvent( HANDLE hEvent // 句柄 ); 设置事件有无信号状态: BOOL SetEvent( HANDLE hEvent // 句柄 ); 关闭事件对象: BOOL CloseHandle( HANDLE hObject // 句柄 );