zoukankan      html  css  js  c++  java
  • VC++中四种进程或线程同步互斥的控制方法

    四种进程或线程同步互斥控制方法
     
         
     
    ChinaITLab收集整理  2006-2-13  保存本文  推荐给好友  QQ上看本站  收藏本站


      很想整理一下自己对进程线程同步互斥理解。正巧周六一个刚刚回到学校同学请客吃饭。在吃饭过程中,有两个同学,为了一个问题争论面红耳赤。一个认为.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. 通过互斥量可以指定资源被独占方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可数据库系统,可以根据用户购买访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号灯对象可以说是一种资源计数器。
  • 相关阅读:
    Docker 学习4 Docker容器虚拟化网络概述
    Ceph 命令
    Day_09【常用API】扩展案例1_程序中使用一个长度为3的对象数组,存储用户的登录名和密码……
    Day_08【面向对象】扩展案例4_年龄为30岁的老王养了一只黑颜色的2岁的宠物……
    Day_08【面向对象】扩展案例3_使用多态的形式创建缉毒狗对象,调用缉毒方法和吼叫方法
    Day_08【面向对象】扩展案例2_测试旧手机新手机类,并给新手机实现玩游戏功能
    Day_08【面向对象】扩展案例1_测试项目经理类和程序员类
    用两个栈实现队列
    二叉树前序、中序、后序遍历相互求法
    洗牌
  • 原文地址:https://www.cnblogs.com/chenhs/p/1270770.html
Copyright © 2011-2022 走看看