监控对象
Last updated
Last updated
监控对象模式会同步并发执行,以确保对象只执行一个方法。并且,还允许对象的方法协同调度执行序列。这种模式也称为线程安全的被动对象模式。
多个线程同时访问一个共享对象时,需要满足以下要求:
并发访问时,需要保护共享对象不受非同步读写操作的影响,以避免数据争用。
必要的同步是实现的一部分,而不是接口的一部分。
当线程处理完共享对象时,需要发送一个通知,以便下一个线程可以使用共享对象。这种机制有助于避免死锁,并提高系统的整体性能。
方法执行后,共享对象的不变量必须保持不变。
客户端(线程)可以访问监控对象的同步方法。因为监控锁在任何时间点上,只能运行一个同步方法。每个监控对象都有一个通知等待客户端的监控条件。
监控对象由四个组件组成。
监控对象:支持一个或多个方法。每个客户端必须通过这些方法访问对象,每个方法都必须在客户端线程中运行。
同步方法:监控对象支持同步方法。任何给定的时间点上,只能执行一个方法。线程安全接口有助于区分接口方法(同步方法)和(监控对象的)实现方法。
监控锁:每个监控对象有一个监控锁,锁可以确保在任何时间点上,只有一个客户端可以访问监控对象。
监控条件:允许线程在监控对象上进行调度。当前客户端完成同步方法的调用后,下一个等待的客户端将被唤醒。
虽然监控锁可以确保同步方法的独占访问,但是监控条件可以保证客户端的等待时间最少。实质上,监控锁可以避免数据竞争,条件监控可以避免死锁。
监控对象及其组件之间的交互具有不同的阶段。
当客户端调用监控对象的同步方法时,必须锁定全局监控锁。如果客户端成功访问,将执行同步方法,并在结束时解锁。如果客户端访问不成功,则阻塞客户端,进入等待状态。
当客户端阻塞时,监控对象会在解锁时,对阻塞的客户端发送通知。通常,等待是资源友好的休眠,而不是忙等。
当客户端收到通知时,会锁定监控锁,并执行同步方法。同步方法结束时解锁,并发送监控条件的通知,以通知下一个客户端去执行。
监控对象的优点和缺点是什么?
优点:
同步方法会完全封装在实现中,所以客户端不知道监控对象会隐式同步。
同步方法将自动调度监控条件的通知/等待机制,其表现类似一个简单的调度器。
缺点:
功能和同步是强耦合的,所以很难改变同步机制。
当同步方法直接或间接调用同一监控对象时,可能会发生死锁。
下面的程序段中定义了一个ThreadSafeQueue。
该示例的核心思想是,将监控对象封装在一个类中,这样就可以重用。监控类使用std::recursive_mutex
作为监控锁,std::condition_variable_any
作为监控条件。与std::condition_variable
不同,std::condition_variable_any
能够接受递归互斥。这两个成员变量都声明为可变,因此可以在常量方法中使用。监控类提供了监控对象的最小支持接口。
第34 - 55行中的ThreadSafeQueue
使用线程安全接口扩展了第53行中的std::queue
。ThreadSafeQueue
继承于监控类,并使用父类的方法来支持同步的方法add
和get
。方法add
和get
使用监控锁来保护监控对象,特别是非线程安全的myQueue
。当一个新项添加到myQueue
时,add
会通知等待线程,并且这个通知是线程安全的。当如ThreadSafeQueue
这样的模板类,将派生类作为基类的模板参数时,这属于C++的一种习惯性用法,称为CRTP:class ThreadSafeQueue: public Monitor<threadsafequeue<T>>
。理解这个习惯的关键是第54行:ThreadSafeQueue<T>& derived = static_cast<threadsafequeue<T>&>(*this)
,该表达式将this
指针向下转换为派生类。监控对象safeQueue
第72行使用(第73行和第74行中的)Lambda函数添加一个数字,或从同步的safeQueue
中删除一个数字。ThreadSafeQueue
本身是一个模板类,可以保存任意类型的值。程序模拟的是100个客户端向safeQueue
添加100个介于1 - 6之间的随机数(第78行)的同时,另外100个客户端从safeQueue
中删除这100个数字。程序会显示使用的线程的编号和id。
奇异递归模板模式(CRTP)
奇异递归模板模式,简单地说,CRTP代表C++中的一种习惯用法,在这种用法中,Derived类派生自类模板Base,因此Base作为Derived模板参数。
理解CRTP习惯用法的关键是,实例化方法是惰性的,只有在需要时才实例化方法。CRTP有两个主要的用例。
静态多态性:静态多态性与动态多态性类似,但与使用虚方法的动态多态性相反,方法调用的分派在编译时进行。
Mixin: Mixin是设计混合代码类时的一个流行概念。
ThreadSafeQueue
使用Mixin技术来扩展它的接口。通过从Monitor
类派生ThreadSafeQueue
,派生类ThreadSafeQueue
获得类Monitor
的所有方法:ThreadSafeQueue: public Monitor<threadsafequeue<T>>
类。惰性C++:CRTP一文中,有对CRTP习语有更深入地描述。
活动对象和监控对象在几个重要的方面类似,但也有不同。这两种体系结构模式,会同步对共享对象的访问。活动对象的方法在不同线程中执行,而监控对象的方法则在同一线程中执行。活动对象更好地将其方法调用与执行解耦,因此更容易维护。