6

假设您在共享内存中有一个引用计数对象。引用计数表示使用对象的进程数,进程负责通过原子指令递增和递减计数,所以引用计数本身也是在共享内存中的(可以是对象的字段,也可以是对象可能包含指向计数的指针,如果他们有助于解决这个问题,我愿意接受建议)。有时,一个进程会有一个错误,阻止它减少计数。您如何尽可能轻松地找出哪个进程没有减少计数?

我想到的一个解决方案是给每个进程一个 UID(也许是他们的 PID)。然后,当进程递减时,它们会将其 UID 推送到与引用计数一起存储的链表上(我选择了链表,因为您可以使用CAS原子地附加到头部)。当你想调试时,你有一个特殊的过程来查看仍然存在于共享内存中的对象的链表,并且无论哪个应用程序的 UID 不在列表中,都是那些尚未减少计数的应用程序的 UID。

此解决方案的缺点是它使用 O(N) 内存,其中 N 是进程数。如果使用共享内存区域的进程数量很大,并且您有大量对象,那么这很快就会变得非常昂贵。我怀疑可能有一个中途解决方案,使用部分固定大小的信息,您可以通过某种方式缩小可能进程的列表来帮助调试,即使您无法查明一个进程。或者,如果您可以检测到只有一个进程没有减少时哪个进程没有减少(即无法处理检测到 2 个或多个未能减少计数的进程),那可能仍然会有很大帮助。

(对于这个问题有更多的“人为”解决方案,比如确保所有应用程序使用同一个库来访问共享内存区域,但是如果共享区域被视为二进制接口并且并非所有进程都将是由你那是你无法控制的。此外,即使所有应用程序都使用同一个库,一个应用程序也可能在库之外有一个错误,以防止减少计数的方式破坏内存。是的,我使用的是一种不安全的语言,比如C/C++ ;)

编辑:在单进程情况下,您将拥有控制权,因此您可以使用RAII(在 C++ 中)。

4

5 回答 5

8

您可以只对每个对象使用一个额外的整数来做到这一点。

将整数初始化为零。当进程增加对象的引用计数时,它会将其 PID 异或到整数中:

object.tracker ^= self.pid;

当一个进程减少引用计数时,它也会这样做。

如果引用计数一直为 1,那么跟踪器整数将等于增加它但没有减少它的进程的 PID。


这是有效的,因为 XOR 是可交换的 ( (A ^ B) ^ C== A ^ (B ^ C)),所以如果一个进程用自己的 PID 对跟踪器进行偶数次异或,它与对它进行异或相同PID ^ PID- 那是零,这使得跟踪器值不受影响。

您也可以使用无符号值(定义为换行而不是溢出) - 在增加使用计数时添加 PID,并在减少它时减去它。

于 2010-02-08T08:12:35.217 回答
1

从根本上说,共享内存共享状态不是一个健壮的解决方案,我不知道如何使它健壮。

最终,如果一个进程退出,它所有的非共享资源都会被操作系统清理掉。顺便说一句,这是使用进程 (fork()) 而不是线程的巨大胜利。

但是,共享资源不是。其他人打开的文件句柄显然没有关闭,并且...共享内存。共享资源仅在共享它们的最后一个进程退出后才会关闭。

想象一下,您在共享内存中有一个 PID 列表。一个进程可以扫描这个列表来寻找僵尸,但是 PID 可以被重用,或者应用程序可能已经挂起而不是崩溃,或者......

我的建议是在每个进程之间使用管道或其他消息传递原语(有时存在自然的主从关系,其他时候都需要与所有人对话)。然后,您可以利用操作系统在进程终止时关闭这些连接,这样您的对等方就会在该事件中收到信号。此外,您可以使用 ping/pong 超时消息来确定对等方是否已挂起。

如果在分析之后,在这些消息中发送实际数据效率太低,您可以将共享内存用于有效负载,只要您将控制通道保持在操作系统清除的某种流上。

于 2010-02-08T07:30:04.093 回答
1

最有效的资源所有权跟踪系统甚至不使用引用计数,更不用说引用持有者列表了。它们只有关于内存中可能存在的每种数据类型的布局的静态信息,还有每个函数的堆栈帧的形状,每个对象都有一个类型指示符。因此,调试工具可以扫描每个线程的堆栈,并递归地跟踪对对象的引用,直到它拥有内存中所有对象的映射以及它们如何相互引用。但是当然,具有这种能力的系统也有自动垃圾收集。他们需要编译器的帮助来获取有关对象布局和堆栈帧的所有信息,而这些信息实际上不能在所有情况下从 C/C++ 可靠地获得(因为对象引用可以存储在联合等中)。

根据您的问题,在“退化”情况下,进程的所有(或几乎所有)状态都将保存在共享内存中 - 除了堆栈上的局部变量。那时,您将在单个进程中拥有与多线程程序完全相同的内容。或者换句话说,共享足够内存的进程开始变得与线程无法区分。

这意味着您不需要指定问题的“多个进程,共享内存”部分。任何人在尝试使用引用计数时都会遇到同样的问题。那些使用线程(或无限制地使用共享内存;同样的事情)的人面临着另一组问题。将两者放在一起,您将拥有一个痛苦的世界。

一般来说,最好不要在线程之间共享可变对象,如果可能的话。具有引用计数的对象是可变的,因为可以修改计数。换句话说,您在(有效)线程之间共享可变对象。

我想说的是,如果您对共享内存的使用足够复杂,以至于需要类似于 GC 的东西,那么您几乎得到了两全其美的情况:在没有进程隔离优势的情况下,进程创建成本高昂。您已经(实际上)编写了一个多线程应用程序,在该应用程序中,您在线程之间共享可变对象。

本地套接字是用于进程间通信的非常跨平台且非常快速的 API;唯一在所有 Unices 和 Windows 上基本相同工作的软件。因此,请考虑将其用作最小的沟通渠道。

顺便说一句,您是否在保存引用的进程中始终使用智能指针?这是您获得引用计数甚至一半正确的唯一希望。

于 2010-02-08T08:11:37.057 回答
0

使用以下

int pids[MAX_PROCS]
int counter;

增量

do
   find i such pid[i]=0  // optimistic
while(cas[pids[i],0,mypid)==false)
my_pos = i;
atomic_inc(counter)

递减

pids[my_pos]=0
atomic_dec(counter);

所以你知道所有使用这个对象的进程

MAX_PROCS足够大并且随机搜索空闲位置,因此如果进程数量显着降低,那么MAX_PROCS搜索将非常快。

于 2010-02-08T08:44:41.390 回答
0

除了自己做事:你还可以使用像 AQTime 这样的工具,它有一个引用计数的 memchecker。

于 2010-02-08T20:13:07.210 回答