进程线程间同步_第1页
进程线程间同步_第2页
进程线程间同步_第3页
进程线程间同步_第4页
进程线程间同步_第5页
已阅读5页,还剩26页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、进程/线程间同步 进程/线程间同步 1. 线程控制2 进程/线程间同步方法2.1 临界区2.2 互斥量 2.3 信号量(灯)2.4 事件1. 线程控制 目前的操作系统大多提供了线程,一些编程工具如VC等语言也提供了线程实现的方法。线程与进程的区别在于子线程与父线程运行在同一进程空间内,而子进程和父进程则运行在不同的空间。这样,同一进程内的不同线程间可以直接通过内存交换数据(出于数据同步原因最好不要这样做)。此外,在Win32的定义中一个进程至少拥有一个线程,所以进程也被叫做主线程。如何创建线程 MFC提供了对线程功能的封装类CWinThread我们常使用的CWinApp类就是从这个类派生的。用

2、户创建线程的函数 AfxBeginThread()CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc, /函数入口地址 LPVOID pParam, / 是传递给线程的参数 int nPriority = THREAD_PRIORITY_NORMAL, /表明线程的优先级 UINT nStackSize = 0, /为栈大小,如果为0表示使用系统默认值 DWORD dwCreateFlags = 0, /为创建线程时的标记,为CREATE_SUSPENDED表示线程被创建后被挂起 LPSECURITY_ATTRIBUTES lpSec

3、urityAttrs = NULL /为安全属性 ); 线程控制AfxBeginThread()函数的返回值是CWinThread类的指针,可以通过它实现对线程的控制。结束线程在线程函数返回时线程将被结束在线程内部可以利用void AfxEndThread( UINT nExitCode );结束线程。 挂起和恢复DWORD CWinThread:SuspendThread( )DWORD CWinThread:ResumeThread( )获取和设置线程优先级int CWinThread:GetThreadPriority( )BOOL CWinThread:SetThreadPriorit

4、y( int nPriority )通过其他API函数对线程进行操作CWinThread类中的成员变量:m_hThread和m_nThreadID保存了线程句柄和线程ID号。可以通过其他API函数对线程进行操作。 在线程外部强制结束一个线程API函数BOOL TerminateThread( HANDLE hThread,DWORD dwExitCode );可以在线程外部强制结束一个线程但这样做是有危险的,因为线程申请某些资源可能没法释放,而且也有可能引起进程的崩溃。推荐的方法为设置一个标记当线程检测到后自己退出,而不是采用从外部强制结束线程的方法。 2 进程/线程间同步方法进程/线程间同步

5、的方法比较多,每种方法都有不同的用途。临界区、互斥量、信号量、事件。操作系统提供了多种同步对象供我们使用,并且可以替我们管理同步对象的加锁和解锁。我们需要做的就是对每个需要同步使用的资源产生一个同步对象,在使用该资源前申请加锁,在使用完成后解锁。 2.1 临界区临界区临界区是一种最简单的同步对象,它只可以在同一进程内部使用。它的作用是保证只有一个线程可以申请到该对象。 相关的API函数: VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection );产生临界区 VOID DeleteCriticalSection(L

6、PCRITICAL_SECTION lpCriticalSection );删除临界区 VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection );进入临界区,相当于申请加锁,如果该临界区正被其他线程使用则该函数会等待到其他线程释放 BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection );进入临界区,相当于申请加锁,和EnterCriticalSection不同如果该临界区正被其他线程使用则该函数会立即返回FALSE,而不会等待 VOID Lea

7、veCriticalSection(LPCRITICAL_SECTION lpCriticalSection );退出临界区,相当于申请解锁 下面的示范代码演示了如何使用临界区来进行数据同步处理: int iCounter=0; /全局变量CRITICAL_SECTION criCounter; / CRITICAL_SECTION是MFC定义的类型DWORD threadA(void* pD)int iID=(int)pD;for(int i=0;im_hThread;hThread1=pT2-m_hThread;hThread2=pT3-m_hThread;/等待这三个线程结束WaitFo

8、rMultipleObjects(3,hThread,TRUE,INFINITE);DeleteCriticalSection(&criCounter); /删除临界区 printf(n over n); 2.2 互斥量 互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用,所以创建互斥量需要的资源更多。如果只为了在进程内部使用的话,用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的,互斥量一旦被创建,就可以通过名字打开它。 用在互斥量上的API函数:HANDLE CreateMutex( /创建互斥量 LPSECURITY_ATTRIBUTES l

9、pMutexAttributes, / 安全信息 BOOL bInitialOwner, / 最初状态,为真,则表示创建它的线程直接拥有了该互斥量,而不需要再申请 LPCTSTR lpName / 名字,可以为NULL,但这样一来就不能被其他线程/进程打开);HANDLE OpenMutex(/打开一个存在的互斥量 DWORD dwDesiredAccess, / 存取方式 BOOL bInheritHandle, / 是否可以被继承 LPCTSTR lpName / 名字);BOOL ReleaseMutex(/释放互斥量的使用权,但要求调用该函数的线程拥有该互斥量的使用权 HANDLE h

10、Mutex / 句柄);BOOL CloseHandle(/关闭互斥量 HANDLE hObject / 句柄); DWORD WaitForSingleObject(/获取互斥量的使用权需要使用函数 HANDLE hHandle, / 等待的对象的句柄 DWORD dwMilliseconds / 等待的时间,以ms为单位,如果为INFINITE表示无限期的等待);其返回值的意义:WAIT_ABANDONED 在等待的对象为互斥量时表明因为互斥量被关闭而变为有信号状态WAIT_OBJECT_0 得到使用权WAIT_TIMEOUT 超过(dwMilliseconds)规定时间 在线程调用Wai

11、tForSingleObject后,如果一直无法得到控制权,线程将被挂起,直到超过时间或是获得控制权。 WaitForSingleObject函数中的对象(Object)的含义这里的对象是一个具有信号状态的对象,对象有两种状态:有信号/无信号。而等待的含义就在于等待对象变为有信号的状态,对于互斥量来讲如果正在被使用则为无信号状态,被释放后变为有信号状态。当等待成功后WaitForSingleObject函数会将互斥量置为无信号状态,这样其他的线程就不能获得使用权而需要继续等待。WaitForSingleObject函数还进行排队功能,保证先提出等待请求的线程先获得对象的使用权 例:演示如何使用

12、互斥量来进行同步(代码的功能还是进行全局变量递增,通过输出结果可以看出,先提出请求的线程先获得了控制权):int iCounter=0;DWORD threadA(void* pD)int iID=(int)pD;/在内部重新打开HANDLE hCounterIn=OpenMutex(MUTEX_ALL_ACCESS,FALSE,sam sp 44);for(int i=0;im_hThread;hThread1=pT2-m_hThread;hThread2=pT3-m_hThread;/等待三个线程都变为有信号状态,也就是说三个线程都结束WaitForMultipleObjects(3,hT

13、hread,TRUE,INFINITE);/ 等待线程CloseHandle(hCounter); /关闭句柄在这里没有使用全局变量来保存互斥量句柄,这并不是因为不能这样做,而是为演示如何在其他的代码段中通过名字来打开已经创建的互斥量。其实这个例子在逻辑上是有一点错误的,因为iCounter这个变量没有跨进程使用,所以没有必要使用互斥量,只需要使用临界区就可以了。从前面的例子中我们看到WaitForSingleObject这个函数将等待一个对象变为有信号状态,那么具有信号状态的对象有哪些呢?下面是一部分: Mutex Event Semaphore Job Process Thread Wai

14、table timer Console input 互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态。所以我们可以使用WaitForSingleObject来等待进程和线程退出。我们在前面的例子中使用了WaitForMultipleObjects函数,这个函数的作用与WaitForSingleObject类似,但从名字上我们可以看出,WaitForMultipleObjects将用于等待多个对象变为有信号状态,函数原型如

15、下: DWORD WaitForMultipleObjects( DWORD nCount, / 等待的对象数量CONST HANDLE *lpHandles, / 对象句柄数组指针 BOOL fWaitAll,/等待方式,为TRUE表示等待全部对象都变为有信号状态才返回,为FALSE表示任何一个对象变为有信号状态则返回 DWORD dwMilliseconds / 超时设置,以ms为单位,如果为INFINITE表示无限期的等待); 2.3 信号量(灯)信号灯对象可以说是一种资源计数器,一般有一个初始值,表示有多少进程/线程可以进入,当信号灯的值大于0时为有信号状态,小于等于0时为无信号状态,

16、所以可以利用WaitForSingleObject进行等待,当WaitForSingleObject等待成功后信号灯的值会被减少1,直到释放时信号灯会被增加1。 用于信号灯操作的API函数 HANDLE CreateSemaphore(/创建信号灯 LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,/ 安全属性,NULL表示使用默认的安全描述 LONG lInitialCount, / 初始值 LONG lMaximumCount, / 最大值 LPCTSTR lpName / 名字);HANDLE OpenSemaphore(/打开信号灯 DWORD d

17、wDesiredAccess, / 存取方式 BOOL bInheritHandle, / 是否能被继承 LPCTSTR lpName / 名字);BOOL ReleaseSemaphore(/释放信号灯 HANDLE hSemaphore, / 句柄 LONG lReleaseCount, / 释放数,让信号灯值增加数 LPLONG lpPreviousCount / 用来得到释放前信号灯的值,可以为NULL);BOOL CloseHandle(/关闭信号灯 HANDLE hObject / 句柄);信号灯的使用方式和互斥量的使用方式非常相似,下面的代码使用初始值为2的信号灯来保证只有两个线

18、程可以同时进行数据库调用:DWORD threadA(void* pD)int iID=(int)pD;/在内部重新打开HANDLE hCounterIn=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,sam sp 44);for(int i=0;im_hThread;hThread1=pT2-m_hThread;hThread2=pT3-m_hThread;/等待线程结束WaitForMultipleObjects(3,hThread,TRUE,INFINITE);/关闭句柄CloseHandle(hCounter);信号灯有时用来作为计数器使用,一般来讲

19、将其初始值设置为0,先调用ReleaseSemaphore来增加其计数,然后使用WaitForSingleObject来减小其计数,遗憾的是通常我们都不能得到信号灯的当前值,但是可以通过设置WaitForSingleObject的等待时间为0来检查信号灯当前是否为0。 2.4 事件 前面讲的信号灯和互斥量可以保证资源被正常的分配和使用,而事件是用来通知其他进程/线程某件操作已经完成。 事件对象可以以两种方式创建,一种为自动重置,在其他线程使用WaitForSingleObject等待到事件对象变为有信号后该事件对象自动又变为无信号状态,一种为人工重置在其他线程使用WaitForSingleOb

20、ject等待到事件对象变为有信号后该事件对象状态不变。例如有多个线程都在等待一个线程运行结束,我们就可以使用人工重置事件,在被等待的线程结束时设置该事件为有信号状态,这样其他的多个线程对该事件的等待都会成功(因为该事件的状态不会被自动重置)。 事件相关的API如下: HANDLE CreateEvent(/创建事件对象 LPSECURITY_ATTRIBUTES lpEventAttributes,/ 安全属性,NULL表示使用默认的安全描述 BOOL bManualReset, / 是否为人工重置 BOOL bInitialState, / 初始状态是否为有信号状态 LPCTSTR lpNa

21、me / 名字);HANDLE OpenEvent(/打开事件对象 DWORD dwDesiredAccess, / 存取方式 BOOL bInheritHandle, / 是否能够被继承 LPCTSTR lpName / 名字);BOOL ResetEvent(/设置事件为无信号状态 HANDLE hEvent / 句柄);BOOL SetEvent(/设置事件有无信号状态 HANDLE hEvent / 句柄);BOOL CloseHandle(/关闭事件对象 HANDLE hObject / 句柄);下面的代码演示了自动重置和人工重置事件在使用中的不同效果: DWORD threadA(

22、void* pD)int iID=(int)pD;/在内部重新打开HANDLE hCounterIn=OpenEvent(EVENT_ALL_ACCESS,FALSE,sam sp 44);printf(tthread %d beginn,iID);/设置成为有信号状态Sleep(1000);SetEvent(hCounterIn);Sleep(1000);printf(tthread %d endn,iID);CloseHandle(hCounterIn);return 0;DWORD threadB(void* pD)/等待threadA结束后在继续执行int iID=(int)pD;/在

23、内部重新打开HANDLE hCounterIn=OpenEvent(EVENT_ALL_ACCESS,FALSE,sam sp 44);if(WAIT_TIMEOUT = WaitForSingleObject(hCounterIn,10*1000)printf(ttthread %d wait time outn,iID);elseprintf(ttthread %d wait okn,iID);CloseHandle(hCounterIn);return 0;/in main functionHANDLE hCounter=NULL;if( (hCounter=OpenEvent(EVEN

24、T_ALL_ACCESS,FALSE,sam sp 44)=NULL)/如果没有其他进程创建这个事件,则重新创建,该事件为人工重置事件hCounter = CreateEvent(NULL,TRUE,FALSE,sam sp 44);/创建线程HANDLE hThread3;printf(test of manual rest eventn);CWinThread* pT1=AfxBeginThread(AFX_THREADPROC)threadA,(void*)1);CWinThread* pT2=AfxBeginThread(AFX_THREADPROC)threadB,(void*)2);CWinThread* pT3=AfxBeginThread(AFX_THREADPROC)threadB,(void*)3);hThread0=pT1-m_hThread;hThread1=pT2-m_hThread;hThread2=pT3-m_hThread;/等待线程结束W

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论