4 种进程或线程同步互斥的控制方法

4 种进程或线程同步互斥的控制方法 [转贴 2006-1-6 13:17:37]    
我顶 字号:

前言:摘自《Windows 内核实验》

1.2.4 windows2000/xp 线程同步

windows2000/xp提供了互斥量(mutex)、信号量(semaphore)和事件(event)等三种同步对象和相应的系统调用,用于线程的互斥与同步。从本质上讲,这组同步对象的功能是相同的,他们的区别在于适用场合和效率会有所不同。

互斥量主要用于共享资源的互斥访问,在一个时刻只能被一个线程使用。他的相关API包括CreateMutex OpenMutex ReleaseMutex。CreateMutex创建一个互斥对象,返回对象句柄;OpenMutex打开并返回一个已存在的互斥对象句柄,用于后续访问;而ReleaseMutex释放对互斥对象的占用,使之成为可用。

信号量对象初始值的取值范围在0到最大值之间,用于限制并发访问的线程数。它的相关api包括:Createsemaphore OpenSemaphore、ReleaseSemphore。Createsemaphore创建一个信号量对象,在输入参数中指定最大值和初值,返回对象句柄;OpenSemaphore返回一个已存在的信号量对象的句柄,用于后续访问;ReleaseSemphore释放对信号量对象的占用。

事件对象相当于触发器,可用于通知一个或多个线程某事件的出现。它的相关API包括CreateEvent OpenEvent SetEvent ResetEvent和PulseEvent。CreateEvent创建一个事件对象,返回对象句柄;OpenEvent 返回一个已存在的事件对象的句柄,用于后续访问。SetEvent 和PulseEvent设置制定事件对象为可用状态;ResetEvent设置指定事件对象为不可用状态。

除了上述三种同步对象,windows2000/xp还提供了一些能与进程同步相关的机制。如临界区对象和互锁变量访问API等。临界区(critical section)对象只能用于在同一进程内使用的临界区,同一进程内各线程对他的访问是互斥进行的。把变量说明为CRITIACAL_SECTION类型,就可作为临界区使用。相关的API包括InitializeCriticalSection、EnterCriticalSection、TryEnterCriticalSection、LeaveCriticalSection和DeleteCriticalSection。initiallizeCriticalSection对临界区对象进行初始化;EnterCriticalSection等待占用临界区的使用权,得到使用权时返回;TryEnterCriticalSection非等待方式申请临界区的使用权,申请失败时,返回0。LeaveCriticalSection释放临界区的使用权;DeleteCriticalSection释放与临界区对象相关的所有系统资源。

互锁变量访问API用于对整型变量的操作,可避免线程间切换对操作连续性的影响。这组互锁变量访问API包括InterlockedExchange、interlockedCompareExchange InterlockedExchangeAdd、InterlockedDecrement Interlockedincrement。InterlockedExchange进行32位数据的限度后写原子操作;interlockedCompareExchange 依据比较结果进行赋值的原子操作;进行限价后村结果的原子操作;InterlockedDecrement 进行先减1后村结果的原子操作;Interlockedincrement进行先加1后村结果的原子操作。

上面提到的各个API函数的细节请查阅MSDN的相关文档。

**********************************************************

Windows平台下的多线程编程


■ 安徽省民航局网络中心 周毅(bqsoft@ah163.com


线程是进程的一条执行路径,它包含独立的堆栈和CPU寄存器状态,每个线程共享所有的进程资源,包括打开的文件、信号标识及动态分配的内存等。一个进程内的所有线程使用同一个地址空间,而这些线程的执行由系统调度程序控制,调度程序决定哪个线程可执行以及什么时候执行线程。线程有优先级别,优先权较低的线程必须等到优先权较高的线程执行完后再执行。在多处理器的机器上,调度程序可将多个线程放到不同的处理器上去运行,这样可使处理器任务平衡,并提高系统的运行效率。

Windows是一种多任务的操作系统,在Windows的一个进程内包含一个或多个线程。32位Windows环境下的Win32 API提供了多线程应用程序开发所需要的接口函数,而利用VC中提供的标准C库也可以开发多线程应用程序,相应的MFC类库封装了多线程编程的类,用户在开发时可根据应用程序的需要和特点选择相应的工具。为了使大家能全面地了解Windows多线程编程技术,本文将重点介绍Win32 API和MFC两种方式下如何编制多线程程序。

多线程编程在Win32方式下和MFC类库支持下的原理是一致的,进程的主线程在任何需要的时候都可以创建新的线程。当线程执行完后,自动终止线程; 当进程结束后,所有的线程都终止。所有活动的线程共享进程的资源,因此,在编程时需要考虑在多个线程访问同一资源时产生冲突的问题。当一个线程正在访问某进程对象,而另一个线程要改变该对象,就可能会产生错误的结果,编程时要解决这个冲突。

Win32 API下的多线程编程

Win32 API是Windows操作系统内核与应用程序之间的界面,它将内核提供的功能进行函数包装,应用程序通过调用相关函数而获得相应的系统功能。为了向应用程序提供多线程功能,Win32 API函数集中提供了一些处理多线程程序的函数集。直接用Win32 API进行程序设计具有很多优点: 基于Win32的应用程序执行代码小,运行效率高,但是它要求程序员编写的代码较多,且需要管理所有系统提供给程序的资源。用Win32 API直接编写程序要求程序员对Windows系统内核有一定的了解,会占用程序员很多时间对系统资源进行管理,因而程序员的工作效率降低。

1. 用Win32函数创建和终止线程

Win32函数库中提供了操作多线程的函数,包括创建线程、终止线程、建立互斥区等。在应用程序的主线程或者其他活动线程中创建新的线程的函数如下:

HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD lpThreadId);

如果创建成功则返回线程的句柄,否则返回NULL。创建了新的线程后,该线程就开始启动执行了。但如果在dwCreationFlags中使用了CREATE_SUSPENDED特性,那么线程并不马上执行,而是先挂起,等到调用ResumeThread后才开始启动线程,在这个过程中可以调用下面这个函数来设置线程的优先权:

BOOL SetThreadPriority(HANDLE hThread,int nPriority);

当调用线程的函数返回后,线程自动终止。如果需要在线程的执行过程中终止则可调用函数:

VOID ExitThread(DWORD dwExitCode);

如果在线程的外面终止线程,则可调用下面的函数:

BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);

但应注意: 该函数可能会引起系统不稳定,而且线程所占用的资源也不释放。因此,一般情况下,建议不要使用该函数。

如果要终止的线程是进程内的最后一个线程,则线程被终止后相应的进程也应终止。

2. 线程的同步

在线程体内,如果该线程完全独立,与其他线程没有数据存取等资源操作上的冲突,则可按照通常单线程的方法进行编程。但是,在多线程处理时情况常常不是这样,线程之间经常要同时访问一些资源。由于对共享资源进行访问引起冲突是不可避免的,为了解决这种线程同步问题,Win32 API提供了多种同步控制对象来帮助程序员解决共享资源访问冲突。在介绍这些同步对象之前先介绍一下等待函数,因为所有控制对象的访问控制都要用到这个函数。

Win32 API提供了一组能使线程阻塞其自身执行的等待函数。这些函数在其参数中的一个或多个同步对象产生了信号,或者超过规定的等待时间才会返回。在等待函数未返回时,线程处于等待状态,此时线程只消耗很少的CPU时间。使用等待函数既可以保证线程的同步,又可以提高程序的运行效率。最常用的等待函数是:

DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);

而函数WaitForMultipleObject可以用来同时监测多个同步对象,该函数的声明为:

DWORD WaitForMultipleObject(DWORD nCount,CONST HANDLE *lpHandles,BOOL bWaitAll,DWORD dwMilliseconds);

(1)互斥体对象

Mutex对象的状态在它不被任何线程拥有时才有信号,而当它被拥有时则无信号。Mutex对象很适合用来协调多个线程对共享资源的互斥访问。可按下列步骤使用该对象:

首先,建立互斥体对象,得到句柄:

HANDLE CreateMutex();

然后,在线程可能产生冲突的区域前(即访问共享资源之前)调用WaitForSingleObject,将句柄传给函数,请求占用互斥对象:

dwWaitResult = WaitForSingleObject(hMutex,5000L);

共享资源访问结束,释放对互斥体对象的占用:

ReleaseMutex(hMutex);

互斥体对象在同一时刻只能被一个线程占用,当互斥体对象被一个线程占用时,若有另一线程想占用它,则必须等到前一线程释放后才能成功。

(2)信号对象

信号对象允许同时对多个线程共享资源进行访问,在创建对象时指定最大可同时访问的线程数。当一个线程申请访问成功后,信号对象中的计数器减一,调用ReleaseSemaphore函数后,信号对象中的计数器加一。其中,计数器值大于或等于0,但小于或等于创建时指定的最大值。如果一个应用在创建一个信号对象时,将其计数器的初始值设为0,就阻塞了其他线程,保护了资源。等初始化完成后,调用ReleaseSemaphore函数将其计数器增加至最大值,则可进行正常的存取访问。可按下列步骤使用该对象:

首先,创建信号对象:

HANDLE CreateSemaphore();

或者打开一个信号对象:

HANDLE OpenSemaphore();

然后,在线程访问共享资源之前调用WaitForSingleObject。

共享资源访问完成后,应释放对信号对象的占用:

ReleaseSemaphore();

(3)事件对象

事件对象(Event)是最简单的同步对象,它包括有信号和无信号两种状态。在线程访问某一资源之前,需要等待某一事件的发生,这时用事件对象最合适。例如:只有在通信端口缓冲区收到数据后,监视线程才被激活。

事件对象是用CreateEvent函数建立的。该函数可以指定事件对象的类和事件的初始状态。如果是手工重置事件,那么它总是保持有信号状态,直到用ResetEvent函数重置成无信号的事件。如果是自动重置事件,那么它的状态在单个等待线程释放后会自动变为无信号的。用SetEvent可以把事件对象设置成有信号状态。在建立事件时,可以为对象命名,这样其他进程中的线程可以用OpenEvent函数打开指定名字的事件对象句柄。

(4)排斥区对象

在排斥区中异步执行时,它只能在同一进程的线程之间共享资源处理。虽然此时上面介绍的几种方法均可使用,但是,使用排斥区的方法则使同步管理的效率更高。

使用时先定义一个CRITICAL_SECTION结构的排斥区对象,在进程使用之前调用如下函数对对象进行初始化:

VOID InitializeCriticalSection(LPCRITICAL_SECTION);

当一个线程使用排斥区时,调用函数:EnterCriticalSection或者TryEnterCriticalSection;

当要求占用、退出排斥区时,调用函数LeaveCriticalSection,释放对排斥区对象的占用,供其他线程使用。

基于MFC的多线程编程

MFC是微软的VC开发集成环境中提供给程序员的基础函数库,它用类库的方式将Win32 API进行封装,以类的方式提供给开发者。由于其快速、简捷、功能强大等特点深受广大开发者喜爱。因此,建议使用MFC类库进行应用程序的开发。

在VC++附带的MFC类库中,提供了对多线程编程的支持,基本原理与基于Win32 API的设计一致,但由于MFC对同步对象做了封装,因此实现起来更加方便,避免了对象句柄管理上的烦琐工作。

在MFC中,线程分为两种:工作线程和用户接口线程。工作线程与前面所述的线程一致,用户接口线程是一种能够接收用户的输入、处理事件和消息的线程。

1. 工作线程

工作线程编程较为简单,设计思路与前面所讲的基本一致: 一个基本函数代表了一个线程,创建并启动线程后,线程进入运行状态; 如果线程用到共享资源,则需要进行资源同步处理。这种方式创建线程并启动线程时可调用函数:

CWinThread*AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam,int nPriority= THREAD_PRIORITY_NORMAL,UINT nStackSize =0,DWORD dwCreateFlags=0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);参数pfnThreadProc是线程执行体函数,函数原形为: UINT ThreadFunction( LPVOID pParam)。

参数pParam是传递给执行函数的参数;

参数nPriority是线程执行权限,可选值:

THREAD_PRIORITY_NORMAL、THREAD_PRIORITY_LOWEST、THREAD_PRIORITY_HIGHEST、THREAD_PRIORITY_IDLE。

参数dwCreateFlags是线程创建时的标志,可取值CREATE_SUSPENDED,表示线程创建后处于挂起状态,调用ResumeThread函数后线程继续运行,或者取值“0”表示线程创建后处于运行状态。

返回值是CWinThread类对象指针,它的成员变量m_hThread为线程句柄,在Win32 API方式下对线程操作的函数参数都要求提供线程的句柄,所以当线程创建后可以使用所有Win32 API函数对pWinThread->m_Thread线程进行相关操作。

注意:如果在一个类对象中创建和启动线程时,应将线程函数定义成类外的全局函数。

2. 用户接口线程

基于MFC的应用程序有一个应用对象,它是CWinApp派生类的对象,该对象代表了应用进程的主线程。当线程执行完并退出线程时,由于进程中没有其他线程存在,进程自动结束。类CWinApp从CWinThread派生出来,CWinThread是用户接口线程的基本类。我们在编写用户接口线程时,需要从CWinThread派生我们自己的线程类,ClassWizard可以帮助我们完成这个工作。

先用ClassWizard派生一个新的类,设置基类为CwinThread。注意:类的DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏是必需的,因为创建线程时需要动态创建类的对象。根据需要可将初始化和结束代码分别放在类的InitInstance和ExitInstance函数中。如果需要创建窗口,则可在InitInstance函数中完成。然后创建线程并启动线程。可以用两种方法来创建用户接口线程,MFC提供了两个版本的AfxBeginThread函数,其中一个用于创建用户接口线程。第二种方法分为两步进行:首先,调用线程类的构造函数创建一个线程对象;其次,调用CWinThread::CreateThread函数来创建该线程。线程建立并启动后,在线程函数执行过程中一直有效。如果是线程对象,则在对象删除之前,先结束线程。CWinThread已经为我们完成了线程结束的工作。

3. 线程同步

前面我们介绍了Win32 API提供的几种有关线程同步的对象,在MFC类库中对这几个对象进行了类封装,它们有一个共同的基类CSyncObject,它们的对应关系为: Semaphore对应CSemaphore、Mutex对应CMutex、Event对应CEvent、CriticalSection对应CCriticalSection。另外,MFC对两个等待函数也进行了封装,即CSingleLock和CMultiLock。因四个对象用法相似,在这里就以CMutex为例进行说明:

创建一个CMutex对象:

CMutex mutex(FALSE,NULL,NULL);

或CMutex mutex;

当各线程要访问共享资源时使用下面代码:

CSingleLock sl(&mutex);

sl.Lock();

if(sl.IsLocked())

//对共享资源进行操作...

sl.Unlock();

结束语

如果用户的应用程序需要多个任务同时进行相应的处理,则使用多线程是较理想的选择。这里,提醒大家注意的是在多线程编程时要特别小心处理资源共享问题以及多线程调试问题。笔者准备了几个实例,如大家需要的话,可以和笔者联系。
 


**********************************************************

     很想整理一下自己对进程线程同步互斥的理解。正巧周六一个刚刚回到学校的同学请客吃饭。在吃饭的过程中,有两个同学,为了一个问题争论的面红耳赤。一个认为.Net下的进程线程控制模型更加合理。一个认为Java下的线程池策略比.Net的好。大家的话题一下转到了进程线程同步互斥的控制问题上。回到家,想了想就写了这个东东。
    现在流行的进程线程同步互斥的控制机制,其实是由最原始最基本的4种方法实现的。由这4种方法组合优化就有了.Net和Java下灵活多变的,编程简便的线程进程控制手段。
    这4种方法具体定义如下 在《操作系统教程》ISBN 7-5053-6193-7 一书中可以找到更加详细的解释
     1临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
     2互斥量:为协调共同对一个共享资源的单独访问而设计的。
     3信号量:为控制一个具有有限数量用户资源而设计。
     4事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
  
    临界区(Critical Section)
    保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。
  临界区包含两个操作原语:
    EnterCriticalSection() 进入临界区
    LeaveCriticalSection() 离开临界区
    EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。否则临界区保护的共享资源将永远不会被释放。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
    MFC提供了很多功能完备的类,我用MFC实现了临界区。MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是非常简单的。只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。Lock()后代码用到的资源自动被视为临界区内的资源被保护。UnLock后别的线程才能访问这些资源。
  
  //CriticalSection
  CCriticalSection global_CriticalSection;
  
  // 共享资源
  char global_Array[256];
  
  //初始化共享资源
  void InitializeArray()
  {
   for(int i = 0;i<256;i++)
   {
   global_Array[i]=I;
   }
  }
  
  //写线程
  UINT Global_ThreadWrite(LPVOID pParam)
  {
   CEdit *ptr=(CEdit *)pParam;
   ptr->SetWindowText("");
   //进入临界区
  global_CriticalSection.Lock();
   for(int i = 0;i<256;i++)
   {
   global_Array[i]=W;
   ptr->SetWindowText(global_Array);
   Sleep(10);
   }
   //离开临界区
   global_CriticalSection.Unlock();
   return 0;
  }
  
  //删除线程
  UINT Global_ThreadDelete(LPVOID pParam)
  {
   CEdit *ptr=(CEdit *)pParam;
   ptr->SetWindowText("");
   //进入临界区
   global_CriticalSection.Lock();
   for(int i = 0;i<256;i++)
   {
   global_Array[i]=D;
   ptr->SetWindowText(global_Array);
   Sleep(10);
   }
   //离开临界区
   global_CriticalSection.Unlock();
   return 0;
  }
  
  //创建线程并启动线程
  void CCriticalSectionsDlg::OnBnClickedButtonLock()
  {
   //Start the first Thread
   CWinThread *ptrWrite = AfxBeginThread(Global_ThreadWrite,
   &m_Write,
   THREAD_PRIORITY_NORMAL,
   0,
   CREATE_SUSPENDED);
   ptrWrite->ResumeThread();
  
   //Start the second Thread
   CWinThread *ptrDelete = AfxBeginThread(Global_ThreadDelete,
   &m_Delete,
   THREAD_PRIORITY_NORMAL,
   0,
   CREATE_SUSPENDED);
   ptrDelete->ResumeThread();
  }
  
    在测试程序中,Lock UnLock两个按钮分别实现,在有临界区保护共享资源的执行状态,和没有临界区保护共享资源的执行状态。
    程序运行结果
    
    互斥量(Mutex)
  
    互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
  
    互斥量包含的几个操作原语:
    CreateMutex() 创建一个互斥量
    OpenMutex() 打开一个互斥量
    ReleaseMutex() 释放互斥量
    WaitForMultipleObjects() 等待互斥量对象
  
    同样MFC为互斥量提供有一个CMutex类。使用CMutex类实现互斥量操作非常简单,但是要特别注意对CMutex的构造函数的调用
    CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL)
    不用的参数不能乱填,乱填会出现一些意想不到的运行结果。
  
  //创建互斥量
  CMutex global_Mutex(0,0,0);
  
  // 共享资源
  char global_Array[256];
  
  void InitializeArray()
  {
   for(int i = 0;i<256;i++)
   {
   global_Array[i]=I;
   }
  }
  UINT Global_ThreadWrite(LPVOID pParam)
  {
   CEdit *ptr=(CEdit *)pParam;
   ptr->SetWindowText("");
   global_Mutex.Lock();
   for(int i = 0;i<256;i++)
   {
   global_Array[i]=W;
   ptr->SetWindowText(global_Array);
   Sleep(10);
   }
   global_Mutex.Unlock();
   return 0;
  }
  
  UINT Global_ThreadDelete(LPVOID pParam)
  {
   CEdit *ptr=(CEdit *)pParam;
   ptr->SetWindowText("");
   global_Mutex.Lock();
   for(int i = 0;i<256;i++)
   {
   global_Array[i]=D;
   ptr->SetWindowText(global_Array);
   Sleep(10);
   }
   global_Mutex.Unlock();
   return 0;
  }
    同样在测试程序中,Lock UnLock两个按钮分别实现,在有互斥量保护共享资源的执行状态,和没有互斥量保护共享资源的执行状态。
    程序运行结果
    
  
    信号量(Semaphores)
    信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的PV操作相同。它指出了同时访问共享资源的线程最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。
  PV操作及信号量的概念都是由荷兰科学家E.W.Dijkstra提出的。信号量S是一个整数,S大于等于零时代表可供并发进程使用的资源实体数,但S小于零时则表示正在等待使用共享资源的进程数。
  P操作 申请资源:
    (1)S减1;
    (2)若S减1后仍大于等于零,则进程继续执行;
    (3)若S减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转入进程调度。
  V操作 释放资源:
    (1)S加1;
    (2)若相加结果大于零,则进程继续执行;
    (3)若相加结果小于等于零,则从该信号的等待队列中唤醒一个等待进程,然后再返回原进程继续执行或转入进程调度。
  
    信号量包含的几个操作原语:
    CreateSemaphore() 创建一个信号量
    OpenSemaphore() 打开一个信号量
    ReleaseSemaphore() 释放信号量
    WaitForSingleObject() 等待信号量
  
  //信号量句柄
  HANDLE global_Semephore;
  
  // 共享资源
  char global_Array[256];
  void InitializeArray()
  {
   for(int i = 0;i<256;i++)
   {
   global_Array[i]=I;
   }
  }
  //线程1
  UINT Global_ThreadOne(LPVOID pParam)
  {
   CEdit *ptr=(CEdit *)pParam;
   ptr->SetWindowText("");
   //等待对共享资源请求被通过 等于 P操作
  WaitForSingleObject(global_Semephore, INFINITE);
   for(int i = 0;i<256;i++)
   {
   global_Array[i]=O;
   ptr->SetWindowText(global_Array);
   Sleep(10);
   }
   //释放共享资源 等于 V操作
   ReleaseSemaphore(global_Semephore, 1, NULL);
   return 0;
  }
  
  UINT Global_ThreadTwo(LPVOID pParam)
  {
   CEdit *ptr=(CEdit *)pParam;
   ptr->SetWindowText("");
   WaitForSingleObject(global_Semephore, INFINITE);
   for(int i = 0;i<256;i++)
   {
   global_Array[i]=T;
   ptr->SetWindowText(global_Array);
   Sleep(10);
   }
   ReleaseSemaphore(global_Semephore, 1, NULL);
   return 0;
  }
  
  UINT Global_ThreadThree(LPVOID pParam)
  {
   CEdit *ptr=(CEdit *)pParam;
   ptr->SetWindowText("");
   WaitForSingleObject(global_Semephore, INFINITE);
   for(int i = 0;i<256;i++)
   {
   global_Array[i]=H;
   ptr->SetWindowText(global_Array);
   Sleep(10);
   }
   ReleaseSemaphore(global_Semephore, 1, NULL);
   return 0;
  }
  
  void CSemaphoreDlg::OnBnClickedButtonOne()
  {
   //设置信号量 1 个资源 1同时只可以有一个线程访问
   global_Semephore= CreateSemaphore(NULL, 1, 1, NULL);
   this->StartThread();
   // TODO: Add your control notification handler code here
  }
  
  void CSemaphoreDlg::OnBnClickedButtonTwo()
  {
   //设置信号量 2 个资源 2 同时只可以有两个线程访问
   global_Semephore= CreateSemaphore(NULL, 2, 2, NULL);
   this->StartThread();
   // TODO: Add your control notification handler code here
  }
  
  void CSemaphoreDlg::OnBnClickedButtonThree()
  {
  //设置信号量 3 个资源 3 同时只可以有三个线程访问
   global_Semephore= CreateSemaphore(NULL, 3, 3, NULL);
   this->StartThread();
   // TODO: Add your control notification handler code here
  }
    信号量的使用特点使其更适用于对Socket(套接字)程序中线程的同步。例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,这时可以为每一个用户对服务器的页面请求设置一个线程,而页面则是待保护的共享资源,通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问,只有不大于设定的最大用户数目的线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。
    程序运行结果
    
  
    事件(Event)
  
    事件对象也可以通过通知操作的方式来保持线程的同步。并且可以实现不同进程中的线程同步操作。
    信号量包含的几个操作原语:
    CreateEvent() 创建一个信号量
    OpenEvent() 打开一个事件
    SetEvent() 回置事件
    WaitForSingleObject() 等待一个事件
    WaitForMultipleObjects()         等待多个事件
      WaitForMultipleObjects 函数原型:
       WaitForMultipleObjects(
       IN DWORD nCount, // 等待句柄数
       IN CONST HANDLE *lpHandles, //指向句柄数组
       IN BOOL bWaitAll, //是否完全等待标志
       IN DWORD dwMilliseconds //等待时间
       )
    参数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定,为TRUE时当所有对象都被通知时函数才会返回,为FALSE则只要其中任何一个得到通知就可以返回。dwMilliseconds在这里的作用与在WaitForSingleObject()中的作用是完全一致的。如果等待超时,函数将返回WAIT_TIMEOUT。
  
  //事件数组
  HANDLE global_Events[2];
  
  // 共享资源
  char global_Array[256];
  
  void InitializeArray()
  {
   for(int i = 0;i<256;i++)
   {
   global_Array[i]=I;
   }
  }
  
  UINT Global_ThreadOne(LPVOID pParam)
  {
   CEdit *ptr=(CEdit *)pParam;
   ptr->SetWindowText("");
   for(int i = 0;i<256;i++)
   {
   global_Array[i]=O;
   ptr->SetWindowText(global_Array);
   Sleep(10);
   }
   //回置事件
   SetEvent(global_Events[0]);
   return 0;
  }
  
  UINT Global_ThreadTwo(LPVOID pParam)
  {
   CEdit *ptr=(CEdit *)pParam;
   ptr->SetWindowText("");
   for(int i = 0;i<256;i++)
   {
   global_Array[i]=T;
   ptr->SetWindowText(global_Array);
   Sleep(10);
   }
   //回置事件
   SetEvent(global_Events[1]);
   return 0;
  }
  
  UINT Global_ThreadThree(LPVOID pParam)
  {
   CEdit *ptr=(CEdit *)pParam;
   ptr->SetWindowText("");
   //等待两个事件都被回置
   WaitForMultipleObjects(2, global_Events, true, INFINITE);
   for(int i = 0;i<256;i++)
   {
   global_Array[i]=H;
   ptr->SetWindowText(global_Array);
   Sleep(10);
   }
   return 0;
  }
  void CEventDlg::OnBnClickedButtonStart()
  {
   for (int i = 0; i < 2; i++)
   {
   //实例化事件
   global_Events[i]=CreateEvent(NULL,false,false,NULL);
   }
   CWinThread *ptrOne = AfxBeginThread(Global_ThreadOne,
   &m_One,
   THREAD_PRIORITY_NORMAL,
   0,
   CREATE_SUSPENDED);
   ptrOne->ResumeThread();
  
   //Start the second Thread
   CWinThread *ptrTwo = AfxBeginThread(Global_ThreadTwo,
   &m_Two,
   THREAD_PRIORITY_NORMAL,
   0,
   CREATE_SUSPENDED);
   ptrTwo->ResumeThread();
  
   //Start the Third Thread
   CWinThread *ptrThree = AfxBeginThread(Global_ThreadThree,
   &m_Three,
   THREAD_PRIORITY_NORMAL,
   0,
   CREATE_SUSPENDED);
   ptrThree->ResumeThread();
   // TODO: Add your control notification handler code here
  }
    事件可以实现不同进程中的线程同步操作,并且可以方便的实现多个线程的优先比较等待操作,例如写多个WaitForSingleObject来代替WaitForMultipleObjects从而使编程更加灵活。
    程序运行结果
    
    总结:
    1. 互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。
    2. 互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态。所以可以使用WaitForSingleObject来等待进程和线程退出。
    3. 通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号灯对象可以说是一种资源计数器。
    疑问:
    在 Linux 上,有两类信号量。第一类是由 semget/semop/semctl API 定义的信号量的 SVR4(System V Release 4)版本。第二类是由 sem_init/sem_wait/sem_post/interfaces 定义的 POSIX 接口。 它们具有相同的功能,但接口不同。 在2.4.x内核中,信号量数据结构定义为(include/asm/semaphore.h)。
  但是在Linux中没有对互斥量的具体提法,只是看到说互斥量是信号量的一种特殊情况,当信号量的最大资源数=1同时可以访问共享资源的线程数=1 就是互斥量了。临界区的定义也比较模糊。没有找到用事件处理线程/进程同步互斥的操作的相关资料。在Linux下用GCC/G++编译标准C++代码,信号量的操作几乎和Windows下VC7的编程一样,不用改多少就顺利移植了,可是互斥量,事件,临界区的Linux移植没有成功。
  
    本文所有事例程序在WindowsXp Sp2 + VC7 下编译通过

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值