【C++】郭老二博文之:C++目录
1、Notifications和Events的区别
1)通知Notifications:如果观察者不知道或不关心事件的来源,则使用通知Notifications。
Poco::NotificationCenter或Poco::NotificationQueue位于源source和目标target之间,并将它们解耦。
通知Notifications可以跨线程发送。
2)事件Events:如果观察者确实关心事件的源source,或者希望仅从特定源source接收事件,则使用事件Events。
事件还支持异步通知和一些Notifications不支持的特性。
3)Notifications和Events的异同
4)Notifications通知的调度流程
5)Events事件的调度流程
2、Notifications 通知
2.1 Poco::Notification
通知类派生自Poco::Notification并支持引用计数(与Poco::AutoPtr兼容)。
通知对象可以保存任意数据并提供任意操作。但是不支持没有复制构造函数、没有赋值函数的值语义,并且总是在堆上创建。
2.2 什么是“值语义”?
有值语义的变量,变量赋值可以转换成内存的逐位复制(bit-wise-copy)。
关于“值语义”请参考博客:https://www.cnblogs.com/ly8838/p/3929025.html
2.3 Poco::NotificationCenter
Poco::NotificationCenter是通知对象的调度程序。
头文件:#include “Poco/NotificationCenter.h”
Poco::NotificationCenter使用观察者对象(Poco::AbstractObserver的子类)告诉它的目标
2.4 订阅subscribe
目标可以通过使用成员函数addObserver()向NotificationCenter注册自己来订阅通知。
或者:使用NotificationCenter::addObserver(const AbstractObserver& observer)注册一个通知目标
订阅可以通过调用removeObserver()来取消。
或者:使用NotificationCenter::removeObserver(const AbstractObserver& observer)取消注册通知目标
2.5 观察者Observer
观察者对象存储一个指向目标对象的指针,以及一个指向目标对象的回调成员函数的指针,并且知道目标对哪些通知感兴趣。
观察者使用Observer或NObserver类模板。
1)对于Observer,接收回调的目标成员函数必须定义为:
void someCallback(SomeNotification* pNf)
其中someecallback可以是任何名称,而SomeNotification是要注册的通知。
回调获得通知对象的共享所有权,并且必须在不再需要它时释放它。
2)对于NObserver,目标成员函数为:
void someCallback(const AutoPtr<SomeNotification>& pNf)
2.6 回调函数Callback
在回调过程中,回调函数可以从NotificationCenter注销自己,或者向NotificationCenter注册新的回调。
在通知期间添加的观察器将在下一次通知时第一次调用。
在通知期间被移除的观察者将不会收到当前通知(除非它们已经收到通知)。
2.7 发布通知
通知由 NotificationCenter::postNotification() 方法发布以供调度。
void postNotification(Notification::Ptr pNotification)向所有订阅了通知类的目标发送通知。
通知下发到所有已注册的目标器。如果目标在处理通知时抛出异常,则调度停止,并将异常传播给调用方。
NotificationCenter拥有通知的所有权。
2.8 多态性
订阅特定通知类的目标也会接收作为该类子类的通知。
如果目标订阅了Poco::Notification,那么它将接收发送到它已注册的NotificationCenter的所有通知。
2.9 示例
$ vi observer.cpp
#include "Poco/NotificationCenter.h"
#include "Poco/Notification.h"
#include "Poco/Observer.h"
#include "Poco/NObserver.h"
#include "Poco/AutoPtr.h"
#include <iostream>
using Poco::NotificationCenter;
using Poco::Notification;
using Poco::Observer;
using Poco::NObserver;
using Poco::AutoPtr;
class BaseNotification: public Notification
{
public:
int id = 101;
};
class SubNotification: public BaseNotification
{
public:
int id = 202;
};
class Target
{
public:
void handleBase(BaseNotification* pNf)
{
std::cout << "handleBase: " << pNf->name() << "; id = " << pNf->id << std::endl;
pNf->release(); // we got ownership, so we must release
}
void handleSub(const AutoPtr<SubNotification>& pNf)
{
std::cout << "handleSub: " << pNf->name() << "; id = " << pNf->id << std::endl;
}
};
int main(int argc, char** argv)
{
NotificationCenter nc;
Target target;
nc.addObserver(Observer<Target, BaseNotification>(target, &Target::handleBase));
nc.addObserver(NObserver<Target, SubNotification>(target, &Target::handleSub));
// 注意:在这里new的,将会在handleBase()中pNf->release()
nc.postNotification(new BaseNotification);
nc.postNotification(new SubNotification);
nc.removeObserver(Observer<Target, BaseNotification>(target, &Target::handleBase));
nc.removeObserver(NObserver<Target, SubNotification>(target, &Target::handleSub));
return 0;
}
编译:
g++ observer.cpp -I ~/git/poco/install/include -L ~/git/poco/install/lib -lPocoFoundationd -lpthread
输出 ;
$ ./a.out
handleBase: 16BaseNotification; id = 101
handleBase: 15SubNotification; id = 101
handleSub: 15SubNotification; id = 202
3、Poco::NotificationQueue异步通知
3.1 说明
Poco::NotificationQueue可用于从一个线程异步地向另一个线程发送通知。
头文件:#include “Poco/NotificationQueue.h”
多个线程可以从NotificationQueue中读取数据。
使用NotificationQueue来:
- 从后台处理线程向用户界面线程发送通知,
- 从控制线程向一个或多个工作线程发送通知。
3.2 入队
void enqueueNotification(Notification::Ptr pNotification)
通知添加到队列的末尾(先进先出 FIFO 原则)。队列获得通知的所有权。
void enqueueUrgentNotification(Notification::Ptr pNotification)
通知添加到队列的开头(后进先出 LIFO 原则)。队列获得通知的所有权。
3.3 出队
Notification* dequeueNotification()
将下一个挂起的通知从队列的开头取出,如果没有可用的通知,则为空。调用方获得通知的所有权。
Notification* waitDequeueNotification()
Notification* waitDequeueNotification(long timeout)
如果没有可用的通知,则等待(最多超时timeout毫秒)发布通知。返回通知,如果没有,则返回null。
3.4 通知完成
如何告诉其它工作线程队列关闭了?
- 为每个工作线程发送一个特殊的QuitNotification;
- 设置一个(全局)停止标志,并使用waitDequeueNotification()带超时;
- 使用wakeUpAll():每次调用waitDequeueNotification()都会立即返回null。
3.5 示例
$ vi notificationQueue.cpp
#include "Poco/Notification.h"
#include "Poco/NotificationQueue.h"
#include "Poco/ThreadPool.h"
#include "Poco/Runnable.h"
#include "Poco/AutoPtr.h"
#include "iostream"
using Poco::Notification;
using Poco::NotificationQueue;
using Poco::ThreadPool;
using Poco::Runnable;
using Poco::AutoPtr;
class WorkNotification: public Notification
{
public:
WorkNotification(int data): _data(data) {
}
int data() const {
return _data; }
private:
int _data;
};
class Worker: public Runnable
{
public:
Worker(NotificationQueue& queue): _queue(queue) {
}
void run()
{
AutoPtr<Notification> pNf(_queue.waitDequeueNotification());
while (pNf)
{
WorkNotification* pWorkNf = dynamic_cast<WorkNotification*>(pNf.get());
if (pWorkNf)
{
std::cout << "hello world!" << std::endl;
}
pNf = _queue.waitDequeueNotification();
}
}
private:
NotificationQueue& _queue;
};
int main(int argc, char** argv)
{
NotificationQueue queue;
Worker worker1(queue); // create worker threads
Worker worker2(queue);
ThreadPool::defaultPool().start(worker1); // start workers
ThreadPool::defaultPool().start(worker2);
for (int i = 0; i < 100; ++i)
{
queue.enqueueNotification(new WorkNotification(i));
}
while (!queue.empty()) // wait until all work is done
Poco::Thread::sleep(100);
queue.wakeUpAll(); // tell workers they're done
ThreadPool::defaultPool().joinAll();
return 0;
}
编译
g++ notificationQueue.cpp -I ~/git/poco/install/include -L ~/git/poco/install/lib -lPocoFoundationd -lpthread
3.6 特殊队列
1)带优先级的队列
Poco::PriorityNotificationQueue
通知时,标记优先级,并按其优先级顺序出队列(数值越低意味着优先级越高)。
2)带时间戳的队列
Poco::TimedNotificationQueue
通知时,标记时间戳,并按照时间戳的顺序出队列
4、Events 事件
4.1 说明
POCO中的事件以c#事件为模型,使用c++方式来实现。
与通知Notifications相反,Events事件是类接口的一部分。
事件支持异步通知、不同的通知策略和自动过期。
4.2 用法
1)定义事件
头文件:#include “Poco/BasicEvent.h”
事件使用Poco::BasicEvent类模板定义。
使用事件参数的类型实例化Poco::BasicEvent
通常,将事件作为公共数据成员添加到类中。
2)委托
头文件:#include “Poco/Delegate.h”
目标通过使用Poco::Delegate类模板注册来订阅事件,向事件注册回调成员函数。
委托是使用事件的+=操作符向事件注册的,类似地,使用-=操作符注销委托。
3)参数
头文件:#include “Poco/EventArgs.h”
一个事件只有一个参数,它可以是一个子类Poco::EventArgs
4)回调函数
与委托一起使用的回调函数必须是具有以下签名之一的函数:
void handler(const void* pSender, EventArg& arg)
第一个参数pSender:指向触发事件的对象
第二个参数arg:是对传递事件的引用。
回调函数可以修改事件参数(除非它已被声明为const)以将数据传递回发送方
5)发射事件
事件可以通过调用其notify()成员函数来同步触发。
事件可以通过调用notifyAsync()成员函数来异步触发。
如果任何事件处理程序抛出异常,则事件调度立即停止,并将异常传播给调用者。
4.3 示例
$ vi event1.cpp
#include "Poco/BasicEvent.h"
#include "Poco/Delegate.h"
#include <iostream>
using Poco::BasicEvent;
using Poco::Delegate;
class Source
{
public:
BasicEvent<int> theEvent;
void setId(int id){
m_id = id;}
int getId(){
return m_id;};
void fireEvent()
{
theEvent(this, m_id);
// theEvent.notify(this, n); // alternative syntax
}
private:
int m_id = 0;
};
class Target
{
public:
void onEvent(const void* pSender, int& arg)
{
std::cout << "onEvent: " << arg << std::endl;
arg = 1987;
}
};
int main(int argc, char** argv)
{
Source source;
Target target;
source.theEvent += Poco::delegate(&target, &Target::onEvent);
source.setId(9527);
source.fireEvent();
std::cout << "source id = " << source.getId() << std::endl;
source.theEvent -= Poco::delegate(&target, &Target::onEvent);
return 0;
}
编译:
$ g++ event1.cpp -I ~/git/poco/install/include -L ~/git/poco/install/lib -lPocoFoundationd -lpthread
输出:
$ ./a.out
onEvent, id = 9527
source id = 1987
4.4 同步事件与异步事件
当事件处理很快 或者 需要同步时,可以使用同步事件notify();
与互斥锁结合使用时,可能造成死锁;
4.5 注意事项
永远不要忘记取消注册委托!否则,悬空指针将在以后的通知中导致未定义的行为(崩溃)。
每个目标只能在一个事件中注册一个委托。如果目标用单个事件注册两个回调函数,则后一个回调函数将替换第一个回调函数。
取消注册从未注册或已经过期的委托是可以的。
事件是线程安全的,也就是说,你可以在通知正在进行时修改委托集。新委托集不会影响当前通知,但会随下一个通知生效
4.6 高级事件
1)Poco::FIFOEvent:可以确保以与添加委托相同的顺序调用委托
2)Poco::PriorityEvent为委托添加优先级
可以使用Poco::FIFOEvent来代替Poco::BasicEvent,以确保以与添加委托相同的顺序调用委托。
必须使用Poco::PriorityDelegate类模板添加委托。委托按其优先级顺序调用,优先级较低的先调用。
3)Poco::Expire:自动自动过期委托
使用Poco::Expire类模板作为Poco::Delegate的包装来定义。