AutoCAD 3DMAX C语言 Pro/E UG JAVA编程 PHP编程 Maya动画 Matlab应用 Android
Photoshop Word Excel flash VB编程 VC编程 Coreldraw SolidWorks A Designer Unity3D
 首页 > VC编程

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

51自学网 2015-08-30 http://www.51zixue.net

 

  程序运行结果


     
   
    

信号量(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 下编译通过。

 
 
说明
:本教程来源互联网或网友上传或出版商,仅为学习研究或媒体推广,51zixue.net不保证资料的完整性。

上一篇:MFC程序的消息处理顺序  下一篇:返回列表