生产者与消费者

来源:互联网 发布:观察者模式 javascript 编辑:程序博客网 时间:2024/05/18 01:57

转载自:http://blog.csdn.net/joseph_happy/article/details/14225087

概念介绍

关于“生产者/消费者”应该所有有过计算机开发经验的人员都有所了解。但在真正的工程开发中,很多人又很容易忽视该模式的应用。具体忽视该模式应用的原因,我总结有两个:一个是对该模式不熟,害怕出现难以控制的bug;另一个是打算使用该模式,但是却无法确定生产者对象和消费者对象之间的数据单元。本篇便是为了解决上述两个问题展开。

实例引出生产者与消费者

解释一个概念,我习惯的做法是通过源码来剖析。不是有位大师说过“源码之下,了无秘密”。下面将会用一个事例代码来对生产者和消费者模式进行简单介绍。

数据单元:

要想使用好生产者和消费者模式,最先要做到的,往往也是最难的一步:确定数据单元

这里例子中生产者生产命令,消费者负责执行(消费)命令。

[cpp] view plaincopy
  1. ///  
  2. /// @brief  
  3. ///     命令接口类  
  4. ///  
  5. class Command  
  6. {  
  7. public:  
  8.      int m_nCmdID;                              ///< 命令唯一标识  
  9. public:  
  10.      virtual void Remove() {delete this;}       ///< 释放命令对象  
  11. }  
接下来我们来定义一系列具体的命令

[cpp] view plaincopy
  1. ///  
  2. /// @brief  
  3. ///       命令类型  
  4. ///  
  5. const int CMD_HERO_NONE        = 0x0000000;   ///< 空命令  
  6. const int CMD_HERO_MOVE_LEFT   = 0x0000001;   ///< 左移命令  
  7. const int CMD_HERO_MOVE_TOP    = 0x0000002;   ///< 上移命令  
  8. const int CMD_HERO_MOVE_RIGHT  = 0x0000003;   ///< 右移命令  
  9. const int CMD_HERO_MOVE_BOTTUM = 0x0000004;   ///< 下移命令  
  10.   
  11. const int CMD_HERO_SKILL_1     = 0x0000010;   ///< 技能1  
  12. const int CMD_HERO_SKILL_2     = 0x0000011;   ///< 技能2  
  13. const int CMD_HERO_SKILL_3     = 0x0000012;   ///< 技能3  
  14. const int CMD_HERO_SKILL_4     = 0x0000013;   ///< 技能4  

[cpp] view plaincopy
  1. ///  
  2. /// @brief  
  3. ///    移动命令  
  4. ///  
  5. class MoveCommand : public Command  
  6. {  
  7. public:  
  8.      MoveCommand(int nStep)  
  9.       : m_nStep(nStep)  
  10.      {  
  11.      }  
  12. public:  
  13.      int m_nStep;                       ///< 移动步数  
  14. }  

[cpp] view plaincopy
  1. ///  
  2. /// @brief  
  3. ///     技能命令  
  4. ///  
  5. class SkillCommand : public Command  
  6. {  
  7. public:  
  8.       SkillCommand()  
  9.       {;}  
  10. }  

以上便是我们具体的数据单元,‘移动命令’负责包含移动命令数据内容,‘技能命令’负责包含技能命令的数据内容。当然乍一看技能命令是一个空类,但机智的读者一定发现‘技能命令’所需要的所有信息都包含在其父类信息中了。

接下来就是我们的生产命令和消费命令过程了。

生产者与消费者

生产者只负责生产命令,消费者负责从生产者生产的命令中去拿取并消费。那么生产者所生产的大量命令,我们该如何去存储呢?很显然的一个做法是使用队列,满足先进先出需求。

[cpp] view plaincopy
  1. const int MAX_COMMAND_COUNT = 1024;                  ///< 最大存储的命令个数  
  2.   
  3. ///  
  4. /// @brief  
  5. ///     命令管理器类  
  6. ///  
  7. class CCommandMgr  
  8. {  
  9. public:  
  10.     CCommandMgr(void);  
  11.     CCommandMgr(const CCommandMgr &rhs);  
  12.     CCommandMgr& operator=(const CCommandMgr &rhs);  
  13. public:  
  14.     virtual ~CCommandMgr(void);  
  15.   
  16.     ///  
  17.     /// @brief  
  18.     ///    获取命令管理对象指针  
  19.     ///  
  20.     static CCommandMgr *GetInstance();  
  21.     ///  
  22.     /// @brief  
  23.     ///    添加操作命令对象  
  24.     ///  
  25.     virtual int AddOperator(Command *cmd);  
  26.     ///  
  27.     /// @brief  
  28.     ///    获取操作命令对象  
  29.     ///  
  30.     virtual Command* GetOperatorToExcute();  
  31.     ///  
  32.     /// @brief  
  33.     ///    删除所有命令对象  
  34.     ///  
  35.     virtual void RemoveAllCommand();  
  36.     ///  
  37.     /// @brief  
  38.     ///    清理资源  
  39.     ///  
  40.     void Release();  
  41. private:  
  42.     HANDLE              m_hListMutex;           ///< 队列锁,每次只能有一个线程访问队列  
  43.     HANDLE              m_hPutSemaphore;        ///< 生产者信号量  
  44.     HANDLE              m_hGetSemaphore;        ///< 消费者信号量  
  45.     std::list<Command*> m_lstCmd;               ///< 命令队列  
  46.   
  47.     static CCommandMgr *m_pCmdMgr;              ///< 命令对象指针  
  48. }  

[cpp] view plaincopy
  1. CCommandMgr* CCommandMgr::m_pCmdMgr = NULL;  
  2.   
  3. CCommandMgr::CCommandMgr(void)  
  4. {  
  5.     m_hListMutex = CreateMutex(NULL,FALSE,NULL);  
  6.     m_hPutSemaphore = CreateSemaphore( NULL, MAX_COMMAND_COUNT, MAX_COMMAND_COUNT, NULL);  
  7.     m_hGetSemaphore = CreateSemaphore( NULL, 0, MAX_COMMAND_COUNT, NULL);  
  8. }  
  9.   
  10. CCommandMgr::~CCommandMgr(void)  
  11. {  
  12. }  
  13.   
  14. CCommandMgr* CCommandMgr::GetInstance()  
  15. {  
  16.     if (NULL == m_pCmdMgr)  
  17.     {  
  18.          m_pCmdMgr = new CCommandMgr();  
  19.     }  
  20.     return m_pCmdMgr;  
  21. }  
  22.   
  23. int CCommandMgr::AddOperator(Command *cmd)  
  24. {  
  25.     WaitForSingleObject(m_hPutSemaphore,INFINITE);  
  26.     WaitForSingleObject(m_hListMutex,INFINITE);  
  27.   
  28.     ///< 可以对命令进行处理,判断是否有重复命令,如果有,则删除重复命令  
  29.     m_lstCmd.push_front(cmd);  
  30.       
  31.     ReleaseSemaphore(m_hGetSemaphore,1,NULL);<span style="color:#ff6666">           </span>///< 在生产者线程中,消费者数据单元+1  
  32.     ReleaseMutex(m_hListMutex);  
  33.     return 0;  
  34. }  
  35.   
  36. Command* CCommandMgr::GetOperatorToExcute()  
  37. {  
  38.     WaitForSingleObject(m_hGetSemaphore,INFINITE);  
  39.     WaitForSingleObject(m_hListMutex,INFINITE);  
  40.   
  41.     Command* pCmd = m_lstCmd.back();  
  42.     m_lstCmd.pop_back();  
  43.   
  44.     ReleaseSemaphore(m_hPutSemaphore,1,NULL);<span style="color:#ff6666">           </span>///< 在消费者线程中,生产者数据单元+1  
  45.     ReleaseMutex(m_hListMutex);  
  46.     return pCmd;  
  47. }  
  48.   
  49. void CCommandMgr::RemoveAllCommand()  
  50. {  
  51.     WaitForSingleObject(m_hListMutex,INFINITE);  
  52.   
  53.     std::list<Command*>::iterator it;  
  54.     it = m_lstCmd.begin();  
  55.     while (it != m_lstCmd.end())                       ///< 删除所有命令,同时对应的所有生产与消费者也必须同步更新  
  56.     {  
  57.          WaitForSingleObject(m_hGetSemaphore,INFINITE);  
  58.          (*it)->Remove();  
  59.          ReleaseSemaphore(m_hPutSemaphore,1,NULL);  
  60.   
  61.          it ++;  
  62.     }  
  63.   
  64.     m_lstCmd.clear();  
  65.     ReleaseMutex(m_hListMutex);  
  66. }  
  67.   
  68. void CCommandMgr::Release()  
  69. {  
  70.     WaitForSingleObject(m_hListMutex,INFINITE);<span style="white-space:pre">           </span>///< 释放命令管理对象  
  71.     RemoveAllCommand();  
  72.   
  73.     CloseHandle(m_hListMutex);  
  74.     CloseHandle(m_hPutSemaphore);  
  75.     CloseHandle(m_hGetSemaphore);  
  76.   
  77.     ReleaseMutex(m_hListMutex);  
  78.     delete m_pCmdMgr;  
  79.     m_pCmdMgr = NULL;  
  80. }  

如上为生产者与消费者处理管理模块,生产者在生产的时候需要判断是否还能继续生产,即判断生产者信号量是否有激活信号,如果有,则继续生产,若没有,只能等到消费者线程完成消费操作后,才能再进行生产;同样消费者线程,只能等到生产者生产有产品之后,才能进行消费。

下面提供一个消费者线程的实现

消费者线程

[cpp] view plaincopy
  1. g_workThread = NULL;  
  2.   
  3. unsigned __stdcall CommandWorkThreadFunc(void *pArg)  
  4. {  
  5.      HWND hNotifyWnd = (HWND)(pArg);  
  6.      if ( NULL == hNotifyWnd)  
  7.      {  
  8.          ASSERT(FALSE);  
  9.          return -1;  
  10.      }  
  11.       
  12.      CCommandMgr* pCmdMgr = CCommandMgr::GetInstance();  
  13.      ASSERT(pCmdMgr);  
  14.        
  15.      while (TRUE)  
  16.      {  
  17.          Command *pCmd = pCmdMgr->GetOperatorToExcute();<span style="color:#ff6666">         </span>///< 消费者取出命令去执行  
  18.          ASSERT(pCmd);  
  19.          Sleep(10);  
  20.          switch(pCmd->m_nCmdID)  
  21.          {  
  22.           case CMD_HERO_MOVE_LEFT:  
  23.           case CMD_HERO_MOVE_TOP:  
  24.           case CMD_HERO_MOVE_RIGHT:  
  25.           case CMD_HERO_MOVE_BUTTOM:  
  26.                {  
  27.                      MoveCommand *pMoveCmd = dynamic_cast<MoveCommand*>(pCmd);  
  28.                      SendMessage(hNotifyWnd, some_self_msg_define, pCmd->m_nCmdID, pMoveCmd->m_nStep);  
  29.                }  
  30.                break;  
  31.          case CMD_HERO_SKILL_1:  
  32.          case CMD_HERO_SKILL_2:  
  33.          case CMD_HERO_SKILL_3:  
  34.          case CMD_HERO_SKILL_4:  
  35.                {  
  36.                     SkillCommand *pSkillCmd = dynamic_cast<SkillCommand*>(pCmd);  
  37.                     SendMessage(hNotifyWnd, some_self_msg_define, pCmd->m_nCmdID, 0);  
  38.                }  
  39.                break;  
  40.          default:  
  41.                break;  
  42.          }  
  43.          pCmd->Remove();<span style="color:#ff6666">            </span>///< 执行完成后,销毁对应的数据单元  
  44.      }  
  45.   
  46.      CCommandMgr::GetInstance()->RemoveAllCommand();  
  47.      return 0;  
  48. }  

如上消费者线程中,消费者不需要知道生产者的任何信息,只要生产者将数据信息放到数据单元,消费者线程就会自动执行,获取到相应的数据信息后,投递给相应的窗口进行处理。

分析

上面的例子代码,不知道是否达到了预期的让读者了解生产者/消费者模式。下面按步骤对上述事例进行阐述。

首先,需要确定我们要生产什么?消费什么?既确定要操作的数据单元。如果在编程中对数据单元没有十足的把握,可以像事例中,定义一个接口数据单元,这样做对后续如果对数据单元增删也是很有益处的。

其次,要实现我们的生产者和消费者管理类,在这里我们采用了信号量来分别控制生产者和消费者。生产者被设定了默认的最大激活信号量,这样做的目的是来控制生产者的生产上限,同时消费者被设定了默认的最小激活信号量,目的是来控制消费者的消费下限。

最后,一般情况下我们都是为消费者做一个消费者线程,这样生产者不论在其他任何线程生产数据单元的时候,消费者就会得到执行。生产者与消费者完全解耦。

如果还有读者不能明白,可以和我继续讨论,我也是正在学习该知识点。

应用环境

生产者/消费者模式,可以应用于所有对消费者执行没有严格控制的地方。因为所有的数据单元一旦被生产者生产,随时都会被消费者所消费使用。在网络通信方面尤其实用,很多客户端需要向服务端发送指令,这时候,就可以采用生产者/消费者模型,客户端无论在什么时候生产指令,消费者都能及时的将数据单元解析,并迅速响应发送给服务端。

研究对象

      生产者

负责数据单元的生产,对数据单元的封装操作。

      消费者

负责数据单元的消费,对数据单元的解析操作。

总结

生产者/消费者虽然不属于23种设计模式中的任何一种,但是它将生产对象与消费对象解耦的过程却和设计模式相成相辅。熟练掌握该模式,会使应用程序的开发更轻便,性能也会相应增强。

PS:事例代码纯手工敲写,并未验证,有误之处请与我联系。

参考

  1. 架构设计:生产者/消费者模式[0]:概述

0 0