Windwos 多线程程序设计

Download Report

Transcript Windwos 多线程程序设计

Windwos 多线程程序设计
1.
2.
3.
4.
多线程概述
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);
• 一般情况下,线程运行结束之后,线程函数正常
返回,但是应用程序可以调用TerminateThread强
行终止某一线程的执行。各参数含义如下:
• hThread:将被终结的线程的句柄;
• dwExitCode:用于指定线程的退出码。
• 使用TerminateThread()终止某个线程的执行是不
安全的,可能会引起系统不稳定;虽然该函数立
即终止线程的执行,但并不释放线程所占用的资
源。因此,一般不建议使用该函数。
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, // 是否能被继承
LPCTSTR lpName // 名字 );
信号量操作函数(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, // 是否能够被继承
• LPCTSTR lpName // 名字 );
事件对象操作函数(2)
•
•
•
•
•
•
设置事件为无信号状态:
BOOL ResetEvent( HANDLE hEvent // 句柄 );
设置事件有无信号状态:
BOOL SetEvent( HANDLE hEvent // 句柄 );
关闭事件对象:
BOOL CloseHandle( HANDLE hObject // 句柄 );