Chinaunix首页 | 论坛 | 博客
  • 博客访问: 229322
  • 博文数量: 36
  • 博客积分: 482
  • 博客等级: 下士
  • 技术积分: 290
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-03 12:14
个人简介

Hi-ho, Silver! 在一个商业英雄辈出的年代,让我们用技术做一次华丽的冒险。向Linus致敬,向Stallman致敬!

文章分类

全部博文(36)

文章存档

2013年(24)

2012年(1)

2011年(8)

2010年(3)

我的朋友

分类: C/C++

2013-03-27 19:01:11

巧妙的C++同步 

概述

由于C++标准库不支持任何多线程编程实用工具,C++编程人员总是必须从头开始创建用于多线程编程的内容(例如线程和同步对象)。
此外,由于C++编程人员总是忙于从头开始创建同步对象,没办法太多地关注同步问题,从而导致处理多个线程的代码中出现较多的bug。本文将介绍几个多线程编程实用工具和惯例,希望
能对您的多线程编程工作有所帮助。

同步对象
大多数现代多任务操作系统都提供了自己的同步API和对象以便进行多线程编程。但是,作为C++编程人员,我们习惯于将所有内容看作对象,因此必须将这些功能封装到类中,并利用C++管理这些API 和对象。
有关提供这些封装类的内容超出了本文范围(从任何有关C++多线程编程的书中都可以找到相关详细实现),但是,本文还是会列出这些类的几个必要接口并进行进一步讨论。


锁用于防止多个线程同时访问一个共享对象,
因为这样可能会损坏共享对象的状态。

点击(此处)折叠或打开

  1. class Lock
  2. {
  3.     public:
  4.         // 获取当前调用线程的锁。
  5.         bool Lockup() const;
  6.         // 释放当前调用线程的锁。
  7.         bool Unlock() const;
  8. };
客户机调用Lock::Lockup()获取共享对象的锁并防止其他线程访问该对象,最后使用完该共享对象后它会调用Lock::Unlock()释放该锁。锁可在多个线程之间共享,一旦某个线程获得该锁,所有其他线程都必须在该锁处阻止,直到所有者释放该锁。

事件
顾名思义,事件用作线程通信的通知。

点击(此处)折叠或打开

  1. class Event
  2. {
  3.     public:
  4.         // 等待在指定的时间内发出此事件。
  5.         bool Wait(unsigned long milliseconds) const;
  6.         // 通知等待线程。
  7.         bool Notify() const;
  8. };
一个客户机调用Event::Wait()等待某个事件的发生,该事件会阻止该客户机,直到另一个客户机调用Event::Notify()引发该事件的通知。通常情况下,等待着的客户机和进行通知的客户机处于不同的线程中,这也是它们需要使用此同步对象才能相互通信的原因。

C++ 实现Form
为了让讨论更加清晰和具体,让我们先从下面这个需要同步支持的例子说起:假设有个叫Form 的类,它含有一个储存其他对象传来消息的队列,并且它执行一个独立的进程处理未决消息。
假设多个线程可能会同时尝试将对象发送到表单中,所以我们必须序列化对表单的访问,尤其是序列化对内部事件队列的访问。因此,Form的典型C++实现应类似如下所示:

点击(此处)折叠或打开

  1. class Form : public Thread
  2. {
  3.     public:
  4.         // 发送指定信息到这个表单
  5.         void PostMessage(const Message& message)
  6.         {
  7.             this->m_messageQueueLock.Lockup(); //[1]
  8.             this->m_messageQueue.PutMessage(message); //[2]
  9.             this->m_messageQueueLock.Unlock(); //[3]
  10.             this->m_messageEvent.Notify(); //[4]
  11.         }
  12.     private:
  13.         // 处理消息队列中的所有未决消息
  14.         void ProcessMessages()
  15.         {
  16.             this->m_messageEvent.Wait(TIME_INFINITE); //[5]
  17.             MessageQueue messagesToProcess; //[6]
  18.             this->m_messageQueueLock.Lockup(); //[7]
  19.             this->m_messageQueue.Swap(messagesToProcess); //[8]
  20.             this->m_messageQueueLock.Unlock(); //[9]
  21.             for (const Message* pMessage=messagesToProcess.GetMessage(); //[10]
  22.                     pMessage; //[11]
  23.                     pMessage = messagesToProcess.GetMessage()) //[12]
  24.             { //[13]
  25.                 this->OnMessage(*pMessage); //[14]
  26.                 messagesToProcess.PopMessage(); //[15]
  27.             } //[16]
  28.         }
  29.         // 进程入口
  30.         virtual void OnRun()
  31.         {
  32.             while (this->IsAlive())
  33.             {
  34.                 this->ProcessMessages();
  35.             }
  36.         }
  37.         // 为子类处理消息
  38.         virtual bool OnMessage (const Message& message) = 0;
  39.     private:
  40.         MessageQueue m_messageQueue;
  41.         Lock m_messageQueueLock;
  42.         Event m_messageEvent;
  43. };
发现这个实现中的问题了吗?给您1 分钟的时间,看是否能找出2 个以上的问题..
好了,时间到。找出问题所在了吗?在此我列出两个问题:

1>>异常安全问题
严格说来,从异常安全的角度来说,PostMessage()和ProcessMessages()很容易出现问题,可能会永久阻止工作线程。
我们假定现在有一个表单正在处理队列中挂起的消息(第[10]-[15]行)。同时,另一个线程中的对象正在尝试将一条消息发送到消息队列中。它成功获得了队列的锁(第[1]行),但不幸的是PutMessage()因为某种原因抛出了一个异常(第[2]行),从而导致该工作线程直接关闭。由于所有者已经关闭,这个锁就没有机会得到释放,所有其他线程都将永久性地阻止在该锁上。这是多么恐怖的事情!
2>>维护问题
如您所见,为了同步对内部消息队列的多个访问,我们必须为其维护两个额外的同步对象(m_messageQueueLock 和m_messageEvent)。如果您有数十个对象要求进行同步,情况会怎样呢?这会是一个非常严重的维护问题,项目越大问题越严重。
借鉴Java 和C#的经验
与C++不同,Java和C#都在语言和开发包中内置了多线程支持和实用工具。现在我们看看这两种语言是如何处理多线程的,有哪些值得我们借鉴的经验(不要感到羞愧,Java和C#从C++借鉴了太多东西,现在我们只是小小地礼尚往来一下)。
鉴于本文介绍的是有关同步惯例的问题,我们将关注一下Java和C#是如何解决在上一节的结尾的问题的。
现在我们讨论一下Java。所有Java对象都从java.lang.Object派生而来,该对象提供了内置的同步功能,例如锁和事件。
Object 类提供的同步接口如下所示:

点击(此处)折叠或打开

  1. class Object {
  2.     public final void wait() throws InterruptedException;
  3.     public final void notify();
  4. };
wait()和notify()方法提供的功能与前面讨论的Event类提供的功能相同。由于所有Java类均派生自Object,所以它们可以随时用作同步对象。非常方便,不是吗?
还有,派生自Object 的所有类(包括Object 本身)都具有一个不可见的内置锁,这个锁不能直接访问,但可通过下面的方式轻松地访问:

点击(此处)折叠或打开

  1. synchronized (this.myObject) {
  2.     this.myObject.doSomething1();
  3.     this.myObject.doSomething2();
  4.     this.myObject.doSomething3();
  5. }
这在Java中称为同步块(C#也提供了类似机制)。进入同步块时,在同步对象上就可以自动获得内置锁;离开同步块时则会自动释放该锁。
此外,如果执行同步块时抛出了任何异常,也可以保证在异常传播出去之前自动释放这个不可见的锁。而且,如果某天您决定不再需要同步doSomething3(),只需上移最后的大括号即可从该块中删除doSomething3()。同样非常方便,不是吗?
好了,现在我们看看如何在Java中实现Form(根据Java编码风格,这里没有将每个方法名的首字母大写):

点击(此处)折叠或打开

  1. abstract class Form extends Thread {
  2.     private MessageQueue messageQueue = new MessageQueue();
  3.     public void postMessage(Message message) {
  4.         synchronized (this.messageQueue) {
  5.               this.messageQueue.putMessage(message);
  6.               this.messageQueue.notify();
  7.         }
  8.     }
  9.     private void processMessages() {
  10.         MessageQueue messagesToProcess =
  11.             new MessageQueue();
  12.         synchronized (this.messageQueue) {
  13.               this.messageQueue.wait();
  14.               this.messageQueue.swap(messagesToProcess);
  15.         }
  16.         for (Message message =
  17.                     messagesToProcess.getMessage();
  18.                     message != null;
  19.                     message = messagesToProcess.getMessage()) {
  20.               this.onMessage(message);
  21.               messagesToProcess.popMessage();
  22.         }
  23.     }
  24.     void onRun() {
  25.         while (this.isAlive()) {
  26.               this.processMessages();
  27.         }
  28.     }
  29.     abstract boolean onMessage (Message message);
  30. };
干净简洁!这样我们就轻松获得了内置的异常安全保证。
一件多么有意义的事情!
但是等一等,作为C++编程人员,我们从这些同步机制可以学到什么?我们不具有任何C++ 语言支持(就某种程度而言,这并不正确);我们也不具有STL 库的任何支持。不过幸运的是,我们可以利用C++强大的可扩展性和灵活性来自行构建支持。

C++ 同步惯例
是的,C++是一种强大的面向对象的编程语言,但是不要忘记,它也是一种强大的泛型编程语言。不过,这与同步有什么关系呢?耐心一点,现在我们看一看可以从泛型编程中获得什么好处。

C++ 同步实用程序
首先,我列出所有我们会用来简化同步的实用程序。

点击(此处)折叠或打开

  1. template<typename T>
  2. struct SyncObject : public T
  3. {
  4.   typedef T ObjectType;
  5.   T& GetObject()
  6.   {
  7.     return *this;
  8.   };
  9.   const T& GetObject() const
  10.   {
  11.     return *this;
  12.   };
  13. };
  14. // 将锁功能注入模板参数。
  15. template<typename T>
  16. struct Lockable : public SyncObject<T>, public Lock
  17. {
  18. };
  19. // 将事件功能注入模板参数。
  20. template<typename T>
  21. struct Waitable : public SyncObject<T>, public Event
  22. {
  23. };
  24. #define SYNCHRONIZED
  25. template<typename T>
  26. struct LockableUtil
  27. {
  28.   typedef T LockableType;
  29.   typedef typename T::ObjectType ObjectType;
  30.   explicit LockableUtil(T& lockable) : m_lockable(lockable)
  31.   {
  32.   }
  33.   T& GetLockable()
  34.   {
  35.     return this->m_lockable;
  36.   }
  37.   const T& GetLockable() const
  38.   {
  39.     return this->m_lockable;
  40.   }
  41.   ObjectType& GetObject()
  42.   {
  43.     return this->m_lockable;
  44.   }
  45.   const ObjectType& GetObject() const
  46.   {
  47.     return this->m_lockable;
  48.   }
  49. private:
  50.   T& m_lockable;
  51. };
  52. template<typename T>
  53. struct AutoLock : public LockableUtil<T>
  54. {
  55.   explicit AutoLock(T& lockable) : LockableUtil<T>(lockable)
  56.   {
  57.     this->GetLockable().Lockup();
  58.   }
  59.   ~AutoLock()
  60.   {
  61.     this->GetLockable().Unlock();
  62.   }
  63. };
C++ 同步惯例
现在我们使用这些实用工具重新实现Form 类,再看看可从中获得什么好处。

点击(此处)折叠或打开

  1. class Form : public Thread
  2. {
  3.     private:
  4.         typedef Lockable< Waitable<MessageQueue> >
  5.             SyncMessageQueue;
  6.     public:
  7.         void PostMessage(const Message& message)
  8.         {
  9.             SYNCHRONIZED
  10.             {
  11.                 AutoLock<SyncMessageQueue>
  12.                     lock(this->m_messageQueue);
  13.                 this->m_messageQueue.PutMessage(message);
  14.             }
  15.             this->m_messageQueue.Notify();
  16.         }
  17. }

转自:http://blog.chinaunix.net/uid-20395183-id-1951539.html
阅读(1450) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~