观察数据变化的不同方式 [英] Different ways of observing data changes

查看:113
本文介绍了观察数据变化的不同方式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的应用程序中,我有很多类。这些类中的大多数都存储相当多的数据,重要的是,如果其中一个数据类的内容发生变化,我的应用程序中的其他模块也会被更新。



这样做的典型方式是这样的:

  void MyDataClass :: setMember(double d)
{
m_member = d;
notifyAllObservers();
}

如果会员不经常更改,而且观察课程需要尽可能快地更新。



另一种观察变化的方式是:

  void MyDataClass :: setMember(double d)
{
setDirty();
m_member = d;
}

如果会员被更改了很多次,这是一个很好的方法,而'观察课程在所有脏实例上定期查看。



不幸的是,我在课堂上混合了两种数据成员。有些改变不是经常(我可以与普通观察者一起生活),其他人被改变了很多次(这在复杂的数学算法中),并且每当值改变会调用观察者将会杀死我的应用程序的性能。



是否还有其他技巧来观察数据更改,或者您可以轻松地组合观察数据更改的几种不同方法的模式?



尽管这是一个与语言无关的问题(我可以尝试用其他语言来理解示例),但是最终的解决方案应该在C ++中运行。

解决方案

您描述的两种方法(概念上)涵盖了两个方面,但是我认为您没有充分解释其优缺点。



有一个你应该注意的项目,这是人口因素。




  • 推送方法很好,当有很多通知和少数观察者

  • P如果有很多通知程序,并且你的观察者应该遍历每一个,那么ull方法是很好的,当没有通知者和许多观察者



他们发现2或3是 dirty ...它不会工作。另一方面,如果您有许多观察员,并且在每次更新时,您需要通知所有这些,那么您可能会注定要注意,因为简单的迭代将会消除您的表现。



有一种可能性,你还没有谈论过:将这两种方法与另一个间接层次结合起来。




    <将每个更改推送到 GlobalObserver
  • 让每个观察者检查 GlobalObserver 需要时



这不是那么容易,因为每个观察者都需要记住上次检查的时间,被通知只是对它还没有观察到的变化。通常的诀窍是使用纪元。

  Epoch 0 Epoch 1 Epoch 2 
event1 event2 ...
... ...

每个观察者记住它需要阅读的下一个纪元(当一个观察者订阅它被给予当前的时代作为回报),并从该时期读取到当前的时代以知道所有的事件。通常情况下,通常情况下不能访问当前时期,例如,您可以决定每次读取请求到达时(如果当前时代不为空),则可以决定切换纪元。



这里的困难是知道何时丢弃时代(当它们不再需要时)。这需要一些引用计数。请记住, GlobalObserver 是将当前时期返回到对象的那个。所以我们为每个时代引入一个计数器,它只是计算出有多少观察者没有观察到这个时期(和后续的)。




  • 在订阅时,我们返回历元编号并递增该时期的计数器

  • 在轮询时,我们减少轮询的时期的计数器,并返回当前时代数,并增加其计数器

  • 在取消订阅时,我们减少时代的计数器 - >确保析构函数取消订阅!



也可以将这与超时组合,注册上次修改时代(即创建下一个),并决定在一段时间后我们可以丢弃它(在这种情况下,我们收回计数器并添加它请注意,该方案可扩展到多线程,因为一个时代可以写入(堆栈中的推送操作),而其他时间是只读的(原子计数器除外)。在不需要分配内存的情况下,可以使用无锁操作来推送堆栈。决定在堆栈完成时切换纪元是非常理智的。


In my application I have many classes. Most of these classes store quite some data, and it is important that other modules in my application are also 'updated' if the content of one of the data classes changes.

The typical way to do this is like this:

void MyDataClass::setMember(double d)
{
m_member = d;
notifyAllObservers();
}

This is a quite good method if the member is not often changed and the 'observing classes' need to be up-to-date as fast as possible.

Another way of observing the changes is this:

void MyDataClass::setMember(double d)
{
setDirty();
m_member = d;
}

This is a good method if the member is changed many times, and the 'observing classes' look at regular intervals at all 'dirty' instances.

Unfortunately, I have a mix of both kinds of data members in my classes. Some are changed not that often (and I can live with normal observers), others are changed many many times (this is within complex mathematical algorithms) and calling the observers everytime the value changes will kill the performance of my application.

Are there any other tricks of observing data changes, or patterns in which you can easily combine several different methods of observing data changes?

Although this is a rather language-independent question (and I can try to understand examples in other languages), the final solution should work in C++.

解决方案

The two methods you've described cover (conceptually) both aspects, however I think you haven't explained sufficiently their pros and cons.

There is one item that you should be aware of, it's the population factor.

  • Push method is great when there are many notifiers and few observers
  • Pull method is great when there are few notifiers and many observers

If you have many notifiers and your observer is supposed to iterate over every of them to discover the 2 or 3 that are dirty... it won't work. On the other hand, if you have many observers and at each update you need to notify all of them, then you're probably doomed because simply iterating through all of them is going to kill your performance.

There is one possibility that you have not talked about however: combining the two approaches, with another level of indirection.

  • Push every change to a GlobalObserver
  • Have each observer check for the GlobalObserver when required

It's not that easy though, because each observer need to remember when was the last time it checked, to be notified only on the changes it has not observed yet. The usual trick is to use epochs.

Epoch 0       Epoch 1      Epoch 2
event1        event2       ...
...           ...

Each observer remembers the next epoch it needs to read (when an observer subscribes it is given the current epoch in return), and reads from this epoch up to the current one to know of all the events. Generally the current epoch cannot be accessed by a notifier, you can for example decide to switch epoch each time a read request arrives (if the current epoch is not empty).

The difficulty here is to know when to discard epochs (when they are no longer needed). This requires reference counting of some sort. Remember that the GlobalObserver is the one returning the current epochs to objects. So we introduce a counter for each epoch, which simply counts how many observers have not observed this epoch (and the subsequent ones) yet.

  • On subscribing, we return the epoch number and increment the counter of this epoch
  • On polling, we decrement the counter of the epoch polled and return the current epoch number and increment its counter
  • On unsubscribing, we decrement the counter of the epoch --> make sure that the destructor unsubscribes!

It's also possible to combine this with a timeout, registering the last time we modified the epoch (ie creation of the next) and deciding that after a certain amount of time we can discard it (in which case we reclaim the counter and add it to the next epoch).

Note that the scheme scales to multithread, since one epoch is accessible for writing (push operation on a stack) and the others are read-only (except for an atomic counter). It's possible to use lock-free operations to push on a stack at the condition that no memory need be allocated. It's perfectly sane to decide to switch epoch when the stack is complete.

这篇关于观察数据变化的不同方式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆