信号量没有破坏/解除竞争条件

时间:2011-08-02 04:43:19

标签: c linux pthreads semaphore race-condition

1 个答案:

答案 0 :(得分:3)

首先,让我提出两种您可能希望考虑的替代方法。

  • 方法#1(X86特定,快速):CMPXCHG8B / CMPXCHG16B。

    x86平台具有双指针宽度的原子比较和交换操作。在32位上,这是8个字节;在64位上有一个CMPXCHG16B,它原子地比较和交换一个完整的 16字节数据。通过使用它,您可以在单个操作中自动交换等待计数和信号量计数。 futex只能在一个指针大小的字段上等待,但在这种情况下这不应该是一个严重的问题。

  • 方法#2(便携式,有限):打包计数。

    如果服务员和信号量计数的限制为2 ^ 16,则只需将两个计数打包在一个32位字段中。

  • 方法#3(便携式,有一些开销):使用信号量信号来保护赛后。

    保留8位信号量计数以锁定后期操作。 post操作将递增此计数器(阻止它是否会溢出),同时增加真正的信号量计数。然后它将与waiters字段一起工作,然后原子地减少锁定计数器。递减后,如果前一个值为255,则唤醒所有服务员(虽然这会导致一个雷鸣般的群体,但它应该非常罕见)。

    删除后,获取锁定255次(您可以在一个步骤中增加多个),并根据需要进行阻止。一旦获得锁定255次,您就知道所有帖子都已完成,删除锁定是安全的。

    下行:帖子现在需要两次原子比较交换,最大信号量计数为2 ^ 24-1。另外,以递归方式输入异常信号处理程序255次将会死锁。

这些方法更简单,更容易证明正确,而且可能更快。然而,它们的局限性可能意味着它们对您的情况是不可接受的(但是CMPXCHG8B方法在x86上应该可以很好地工作)。还有一个:

  • 方法#4(有点独立;复杂;快速):修改内核

    这里的一个选择是修改内核以允许用于读取服务器字段的低开销,安全的方法,而不会在释放内存时导致段错误。例如,您可以添加一个注册特定于线程的数据结构的系统调用;在该特定于线程的数据页面中,您可以拥有“错误处理程序跳转地址”。如果程序段错误,如果跳转地址非零,内核只是跳转到那里而不是提升SIGSEGV。或者,您可以使用类似的机制来简单地抑制违规指令。

    现在你所要做的就是:

    • 在libc init和线程启动时,注册这些特定于线程的结构,并在TLS数据中保存指向它们的指针
    • 在帖子中,安排在服务员数量周围抑制故障。如果确实发生了故障,请不要进行唤醒(如果相关内存被重用用于其他用途,则唤醒无害)

    然后你去了 - 你得到了快速的算法,但你也可以获得对删除竞赛的保护。但你必须破解内核的segv处理程序才能做到这一点。可能值得在Windows上查看SEH;类似的机制在这里可以很好地发挥作用。

在任何演员阵容中,我认为你的方法没有任何问题,但我可能会遗漏一些东西。在适当的邮件列表上提出它并与futex维护者协商可能会很好;他们可能会对在内核中实现支持感兴趣,以便让您更轻松。