进程线程间同步

  • 格式:doc
  • 大小:106.50 KB
  • 文档页数:14

进程/线程间同步这一节的内容比较多请你耐心的看完,因为进程/线程间同步的方法比较多,每种方法都有不同的用途:这节中会讲通过临界区,互斥量,信号灯,事件来进行同步。

由于进程/线程间的操作是并行进行的,所以就产生了一个数据的同步问题,我们先看一段代码:int iCounter=0;//全局变量DOWRD threadA(void* pD){for(int i=0;i<100;i++){int iCopy=iCounter;//Sleep(1000);iCopy++;//Sleep(1000);iCounter=iCopy;}}现在假设有两个线程threadA1和threadA2在同时运行,那么运行结束后iCounter的值会是多少,是200吗?不是的,如果我们将Sleep(1000)前的注释去掉后我们会很容易明白这个问题,因为在iCounter 的值被正确修改前它可能已经被其他的线程修改了。

这个例子是一个将机器代码操作放大的例子,因为在CPU内部也会经历数据读/写的过程,而在线程执行的过程中线程可能被中断而让其他线程执行。

变量iCounter在被第一个线程修改后,写回内存前如果它又被第二个线程读取,然后才被第一个线程写回,那么第二个线程读取的其实是错误的数据,这种情况就称为脏读(dirty read)。

这个例子同样可以推广到对文件,资源的使用上。

那么要如何才能避免这一问题呢,假设我们在使用iCounter前向其他线程询问一下:有谁在用吗?如果没被使用则可以立即对该变量进行操作,否则等其他线程使用完后再使用,而且在自己得到该变量的控制权后其他线程将不能使用这一变量,直到自己也使用完并释放为止。

经过修改的伪代码如下:int iCounter=0;//全局变量DWORD threadA(void* pD){for(int i=0;i<100;i++){ask to lock iCounterwait other thread release the locklock successful{int iCopy=iCounter;//Sleep(1000);iCopy++;}iCounter=iCopy;release lock of iCounter}}幸运的是OS提供了多种同步对象供我们使用,并且可以替我们管理同步对象的加锁和解锁。

我们需要做的就是对每个需要同步使用的资源产生一个同步对象,在使用该资源前申请加锁,在使用完成后解锁。

接下来我们介绍一些同步对象:临界区临界区是一种最简单的同步对象,它只可以在同一进程内部使用。

它的作用是保证只有一个线程可以申请到该对象,例如上面的例子我们就可以使用临界区来进行同步处理。

几个相关的API函数为: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 );退出临界区,相当于申请解锁下面的示范代码演示了如何使用临界区来进行数据同步处理://全局变量int iCounter=0;CRITICAL_SECTION criCounter;DWORD threadA(void* pD){int iID=(int)pD;for(int i=0;i<8;i++){EnterCriticalSection(&criCounter);int iCopy=iCounter;Sleep(100);iCounter=iCopy+1;printf("thread %d : %d\n",iID,iCounter);LeaveCriticalSection(&criCounter);}return 0;}//in main function{//创建临界区InitializeCriticalSection(&criCounter);//创建线程HANDLE hThread[3];CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);hThread[0]=pT1->m_hThread;hThread[1]=pT2->m_hThread;hThread[2]=pT3->m_hThread;//等待线程结束//至于WaitForMultipleObjects的用法后面会讲到。

WaitForMultipleObjects(3,hThread,TRUE,INFINITE);//删除临界区DeleteCriticalSection(&criCounter);printf("\nover\n");}互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。

所以创建互斥量需要的资源更多,所以如果只为了在进程内部使用的话使用临界区会带来速度上的优势并能够减少资源占用量。

因为互斥量是跨进程的。

互斥量一旦被创建,就可以通过名字打开它。

下面介绍可以用在互斥量上的API函数:创建互斥量:HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,// 安全信息BOOL bInitialOwner, // 最初状态,//如果设置为真,则表示创建它的线程直接拥有了该互斥量,而不需要再申请LPCTSTR lpName // 名字,可以为NULL,但这样一来就不能被其他线程/进程打开);打开一个存在的互斥量:HANDLE OpenMutex(DWORD dwDesiredAccess, // 存取方式BOOL bInheritHandle, // 是否可以被继承LPCTSTR lpName // 名字);释放互斥量的使用权,但要求调用该函数的线程拥有该互斥量的使用权:BOOL ReleaseMutex(//作用如同LeaveCriticalSectionHANDLE hMutex // 句柄);关闭互斥量:BOOL CloseHandle(HANDLE hObject // 句柄);你会说为什么没有名称如同EnterMutex,功能如同EnterCriticalSection一样的函数来获得互斥量的使用权呢?的确没有!获取互斥量的使用权需要使用函数:DWORD WaitForSingleObject(HANDLE hHandle, // 等待的对象的句柄DWORD dwMilliseconds // 等待的时间,以ms为单位,如果为INFINITE表示无限期的等待);返回:WAIT_ABANDONED 在等待的对象为互斥量时表明因为互斥量被关闭而变为有信号状态WAIT_OBJECT_0 得到使用权WAIT_TIMEOUT 超过(dwMilliseconds)规定时间在线程调用WaitForSingleObject后,如果一直无法得到控制权线程将被挂起,直到超过时间或是获得控制权。

讲到这里我们必须更深入的讲一下WaitForSingleObject函数中的对象(Object)的含义,这里的对象是一个具有信号状态的对象,对象有两种状态:有信号/无信号。

而等待的含义就在于等待对象变为有信号的状态,对于互斥量来讲如果正在被使用则为无信号状态,被释放后变为有信号状态。

当等待成功后WaitForSingleObject函数会将互斥量置为无信号状态,这样其他的线程就不能获得使用权而需要继续等待。

WaitForSingleObject函数还进行排队功能,保证先提出等待请求的线程先获得对象的使用权,下面的代码演示了如何使用互斥量来进行同步,代码的功能还是进行全局变量递增,通过输出结果可以看出,先提出请求的线程先获得了控制权: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;i<8;i++){printf("%d wait for object\n",iID);WaitForSingleObject(hCounterIn,INFINITE);int iCopy=iCounter;Sleep(100);iCounter=iCopy+1;printf("\t\tthread %d : %d\n",iID,iCounter);ReleaseMutex(hCounterIn);}CloseHandle(hCounterIn);return 0;}//in main function{//创建互斥量HANDLE hCounter=NULL;if( (hCounter=OpenMutex(MUTEX_ALL_ACCESS,FALSE,"sam sp 44"))==NULL){//如果没有其他进程创建这个互斥量,则重新创建hCounter = CreateMutex(NULL,FALSE,"sam sp 44");}//创建线程HANDLE hThread[3];CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);hThread[0]=pT1->m_hThread;hThread[1]=pT2->m_hThread;hThread[2]=pT3->m_hThread;//等待线程结束WaitForMultipleObjects(3,hThread,TRUE,INFINITE);//关闭句柄CloseHandle(hCounter);}}在这里我没有使用全局变量来保存互斥量句柄,这并不是因为不能这样做,而是为演示如何在其他的代码段中通过名字来打开已经创建的互斥量。