对象的序列化:不能涉及线程状态,对吧



我正在认真研究将正在执行的程序的状态存储到磁盘并再次将其恢复的基本原则。 在我们当前的设计中,每个对象(这是一个带有函数指针列表的 C 级东西,一种低级自制的面向对象 - 并且有很好的理由这样做)将被调用以将其显式状态导出为可写和可恢复的格式。 使这项工作的关键属性是,与对象相关的所有状态确实都封装在对象数据结构中。

还有其他解决方案,您可以在其中使用活动对象,其中有一个用户级线程附加到某些对象。因此,程序计数器、寄存器内容和堆栈内容突然成为程序状态的一部分。 据我所知,没有好的方法可以在任意时间点将这些东西序列化到磁盘。 线程必须将自己停放在某种特殊状态,其中程序计数器等不表示任何内容,因此基本上将其执行状态机器状态"保存"为显式对象状态。

我已经查看了一系列序列化库,据我所知,这是一个通用属性。

核心问题是:或者实际上并非如此?是否有可以包含线程状态的保存/还原解决方案,就线程在其代码中执行的位置而言?

请注意,在

虚拟机中保存整个系统状态不算在内,这不是真正序列化状态,而只是冻结计算机并移动它。 这是一个显而易见的解决方案,但大多数时候有点重量级。

有些问题清楚地表明,我在解释我们如何做事的想法时不够清楚。 我们正在开发一个模拟器系统,允许编写非常严格的规则,允许在其中运行代码。特别是,我们在对象构造和对象状态之间进行了完全划分。 每次设置系统时都会重新创建接口函数指针,并且不是状态的一部分。 状态仅由特定的指定"属性"组成,每个属性都有一个定义的 get/set 函数,该函数在内部运行时表示和存储表示之间转换。 对于对象之间的指针,它们都转换为名称。 因此,在我们的设计中,一个对象可能会像这样在存储中出现:

Object foo {
  value1: 0xff00ff00;
  value2: 0x00ffeedd;
  next_guy_in_chain: bar;
}
Object bar {
  next_guy_in_chain: null;
}

链表从未真正存在于模拟结构中,每个对象代表某种硬件单元。

问题是有些人想要这样做,但也将线程作为编码行为的一种方式。 这里的"行为"实际上是模拟单元状态的突变。 基本上,我们的设计说所有这些更改都必须在原子完整操作中进行,这些操作被调用,完成它们的工作,然后返回。 所有状态都存储在对象中。 你有一个反应式模型,或者它可以称为"运行到完成"或"事件驱动"。

另一种思考方式是让对象有活动线程在它们上面工作,它们以与经典 Unix 线程相同的方式坐在永恒循环中,并且永远不会终止。 在这种情况下,我试图看看它是否可以合理地存储到磁盘,但如果不在下面插入 VM,这似乎不可行。

2009 年 10 月更新:在 2009 年的 FDL 会议上发表了与此相关的论文,请参阅这篇关于检查点和 SystemC 的论文。

我不认为仅序列化程序的"某些线程"可以工作,因为您会遇到同步问题(此处 http://java.sun.com/j2se/1.3/docs/guide/misc/threadPrimitiveDeprecation.html 描述了一些问题)。因此,坚持整个程序是获得一致状态的唯一可行方法。

您可能会研究的是正交持久性。有一些原型实现:

http://research.sun.com/forest/COM.Sun.Labs.Forest.doc.external_www.PJava.main.html

http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.17.7429

但是它们都不再得到维护或获得很大的吸引力(afaik)。我想检查点毕竟不是最好的解决方案。在我自己的项目中 http://www.siebengeisslein.org 我正在尝试使用轻量级事务来调度事件的方法,因此不必维护线程状态(因为在事务结束时,线程调用堆栈再次为空,如果操作在事务中途停止,则所有内容都会回滚,因此线程调用堆栈也很重要)。您可能可以使用任何 OODBMS 实现类似的东西。

另一种看待事物的方法是延续(http://en.wikipedia.org/wiki/Continuation,http://jauvm.blogspot.com/)。它们是在定义的代码位置挂起执行的一种方法(但它们不一定保留线程状态)。

我希望这能给你一些起点(但是这个afaik没有现成的解决方案)。

编辑:阅读您的澄清后:您绝对应该查看OODBMS。在自己的事务中调度每个事件,而不关心线程。

这听起来确实像保存虚拟机的状态并能够以完全相同的方式还原它正是您想要的。

如果您所需要的只是能够使用与上一次执行相同的数据启动程序运行,那么您只需要保存并恢复持久数据,每个线程的确切状态应该并不重要,因为它无论如何都会变化得如此之快 - 并且下次事物的实际地址会有所不同。无论如何,使用数据库应该可以为您提供这种能力。

比尝试序列化程序状态更好的方法是使用数据检查点实现仅崩溃软件。如何执行数据检查点将取决于您的实现和问题域。

看起来你想在C++有一个闭包。正如你所指出的,语言中没有内置的机制来让你做到这一点。据我所知,这基本上不可能以完全笼统的方式进行。通常,使用没有 VM 的语言很难做到这一点。您可以通过执行一些类似于您建议基本上创建一个维护执行环境/状态的闭包对象来伪造它。然后,当它处于已知状态时,让它序列化自身。

您还会遇到函数指针的问题。每次加载时,可以将函数加载到不同的内存地址。

我认为线程状态是一个可能不适合序列化的实现细节。 您希望保存对象的状态 - 不一定是它们如何成为现在的样子。

作为为什么要采用此方法的示例,请考虑无中断升级。 如果您正在运行应用程序的版本 N 并希望升级到版本 N+1,则可以使用对象序列化来实现。 但是,"版本 N+1"线程将与版本 N 线程不同。

您不应该尝试序列化程序必须磁盘的状态。因为除非操作系统允许,否则您的程序将永远无法完全控制其状态,在这种情况下......它是操作系统的一部分。

您无法保证指向某个虚拟内存位置的指针将再次指向同一虚拟内存位置(堆开始/结束、堆栈开始等属性除外),因为对于程序来说,操作系统对虚拟内存的选择是不确定的。您通过 sbrk 或更高级别的接口(如 malloc)从操作系统请求的页面将在任何地方开始。

更好:

  • 代码清理并检查设计:哪些状态属性是其中的一部分?
  • 不要使用这种低级语言,因为创建您尝试执行的操作的开销不值得结果。
  • 如果必须使用 C,请考虑使生活尽可能轻松的方法(考虑 offsetof 运算符和属性结构具有从偏移量 0 开始的第一个成员)。

我怀疑您想缩短序列化/反序列化特定数据结构(例如链表)所需的开发时间。请放心,您正在尝试做的事情并非微不足道,而是要做更多的工作。如果您坚持这样做,请考虑查看操作系统的内存管理代码和操作系统的分页机制。;-)

由于附加的问题而编辑:你陈述的设计听起来像某种状态机;对象属性的设置使得它们是可序列化的,函数指针可以恢复。

首先,关于对象中的线程状态:这些只有在可能存在典型的并发编程问题(如竞争条件等)时才有意义。如果是这种情况,则需要线程同步功能,例如互斥锁、信号量等。然后,您可以随时访问属性以进行序列化/反序列化并确保安全。

其次,关于对象设置:看起来很酷,不确定您使用的是二进制对象还是其他对象表示形式。假设二进制:如果可以在内存中表示实际结构(这是一些编码开销),则可以轻松序列化它们。在对象的开头插入某种类 ID 值,并有一个指向实际服装的查找表。查看第一个大小(id)字节,您就知道您拥有哪种结构。然后你就会知道那里躺着哪个结构。

序列化/反序列化时,可以这样处理问题:您可以查找假设打包(成员之间没有间距)结构的长度,分配该大小并一个接一个地读取/写入成员。考虑偏移量,或者,如果您的编译器支持它,只需使用打包结构。

由于粗体核心问题而编辑:-) 不,没有; 不适用于 C。

在JSR 323中,实际上为Java提出了这样的东西:

http://tech.puredanger.com/2008/01/09/strong-mobility-for-java/

但因过于理论化而未被接受:

http://tech.puredanger.com/2008/01/24/jcp-votes-down-jsr-323/

如果您点击链接,您可以找到有关此问题的一些有趣研究。

最新更新