如何验证无锁算法?

时间:2010-01-15 13:43:03

标签: algorithm verification lock-free

理论上,应该可以至少强制执行无锁算法的验证(只有很多函数调用的组合相交)。是否有任何工具或形式推理过程可以实际证明无锁算法是正确的(理想情况下它也应该能够检查竞争条件和ABA问题)?

注意:如果您知道一种方法来证明一点(例如,只证明它对ABA问题是安全的)或者我没有提到的问题,那么无论如何都要发布解决方案。在最坏的情况下,可以依次完成每种方法以完全验证它。

5 个答案:

答案 0 :(得分:20)

你一定要试试Spin model checker

你用一种名为Promela的简单类C语言编写类似程序的模型,Spin内部转换为状态机。模型可以包含多个并行进程。

Spin会做什么,检查每个进程的每个可能的指令交错 你要测试的任何条件 - 通常,没有竞争条件,没有死锁大多数这些测试都可以使用assert()语句轻松编写。如果有任何可能的执行序列违反了断言,则打印出序列,否则您将获得“全清除”。

(实际上,它使用了更加高效和快速的算法来实现这一目标,但这就是效果。默认情况下,会检查所有可访问的程序状态。)

这是一个令人难以置信的计划,它赢得了2001 ACM System Software Award(其他获奖者包括Unix,Postscript,Apache,TeX)。我开始非常快速地使用它,并且在几天内能够在Promela中实现MPI函数MPI_Isend()MPI_Irecv()的模型。 Spin在我转换到Promela进行测试的一段并行代码中发现了一些棘手的竞争条件。

答案 1 :(得分:8)

旋转确实非常出色,但也考虑到Dmitriy V'jukov的Relacy Race Detector。它专门用于验证并发算法,包括非阻塞(无等待/无锁)算法。它是开源的,并获得自由许可。

Relacy提供POSIX和Windows同步原语(互斥,条件变量,信号量,CriticalSections,win32事件,Interlocked *等),因此您可以将实际的C ++实现提供给Relacy进行验证。无需像Promela和Spin那样开发算法的单独模型。

Relacy提供C ++ 0x std::atomic(获胜的显式内存排序!),因此您可以使用预处理器#defines在Relacy的实现和您自己的平台特定原子实现之间进行选择({{ 3}},tbb::atomic等)。

调度是可控的:随机,上下文绑定和完整搜索(所有可能的交错)。

以下是Relacy计划的示例。有几点需要注意:

  • $是一个记录执行信息的Relacy宏。
  • rl::var<T>标记“正常”(非原子)变量,这些变量也需要被视为验证的一部分。

代码:

#include <relacy/relacy_std.hpp>

// template parameter '2' is number of threads
struct race_test : rl::test_suite<race_test, 2>
{
    std::atomic<int> a;
    rl::var<int> x;

    // executed in single thread before main thread function
    void before()
    {
        a($) = 0;
        x($) = 0;
    }

    // main thread function
    void thread(unsigned thread_index)
    {
        if (0 == thread_index)
        {
            x($) = 1;
            a($).store(1, rl::memory_order_relaxed);
        }
        else
        {
            if (1 == a($).load(rl::memory_order_relaxed))
                x($) = 2;
        }
    }

    // executed in single thread after main thread function
    void after()
    {
    }

    // executed in single thread after every 'visible' action in main threads
    // disallowed to modify any state
    void invariant()
    {
    }
};

int main()
{
    rl::simulate<race_test>();
}

使用普通编译器编译(Relacy仅限标头)并运行可执行文件:

struct race_test
DATA RACE
iteration: 8

execution history:
[0] 0:  atomic store, value=0, (prev value=0), order=seq_cst, in race_test::before, test.cpp(14)
[1] 0:  store, value=0, in race_test::before, test.cpp(15)
[2] 0:  store, value=1, in race_test::thread, test.cpp(23)
[3] 0:  atomic store, value=1, (prev value=0), order=relaxed, in race_test::thread, test.cpp(24)
[4] 1:  atomic load, value=1, order=relaxed, in race_test::thread, test.cpp(28)
[5] 1:  store, value=0, in race_test::thread, test.cpp(29)
[6] 1: data race detected, in race_test::thread, test.cpp(29)

thread 0:
[0] 0:  atomic store, value=0, (prev value=0), order=seq_cst, in race_test::before, test.cpp(14)
[1] 0:  store, value=0, in race_test::before, test.cpp(15)
[2] 0:  store, value=1, in race_test::thread, test.cpp(23)
[3] 0:  atomic store, value=1, (prev value=0), order=relaxed, in race_test::thread, test.cpp(24)

thread 1:
[4] 1:  atomic load, value=1, order=relaxed, in race_test::thread, test.cpp(28)
[5] 1:  store, value=0, in race_test::thread, test.cpp(29)
[6] 1: data race detected, in race_test::thread, test.cpp(29)

最近版本的Relacy还提供了Java和CLI内存模型,如果你是这样的话。

答案 2 :(得分:4)

我不知道您使用的是什么平台或语言 - 但在.Net平台上有一个名为Chess的微软研究项目,它看起来非常有希望帮助我们这些人做多线程组件 - 包括锁定自由。

我还没有大量使用它。

通过以最严格的方式显式交错线程来实际强制你的bug进入野外,它起作用(粗略解释)。它还分析代码以查找常见错误和错误模式 - 类似于代码分析。

过去,我还构建了有问题的代码的特殊版本(通过#if块等),添加了额外的状态跟踪信息;计数,版本等我可以在单元测试中进行考虑。

问题在于,编写代码需要花费更多的时间,并且在不改变已经存在的代码的底层结构的情况下,不能总是添加这种东西。

答案 3 :(得分:4)

数据竞争检测是NP难题[Netzer&amp; Miller 1990]

我听说过工具Lockset和DJit +(他们在CDP课程中teach it)。 尝试阅读幻灯片,并使用Google搜索引用的内容。它包含一些有趣的信息。

答案 4 :(得分:4)

如果你想真正验证无锁代码(而不是详尽地测试一个小实例),你可以使用VCC(http://vcc.codeplex.com),一个用于并发C代码的演绎验证器,用于验证一些有趣的无锁算法(例如,使用危险指针的无锁列表和可调整大小的哈希表,乐观多版本事务处理,MMU虚拟化,各种同步原语等)。它进行模块化验证,并用于验证工业代码的重要组块(最高约20KLOC)。

但请注意,VCC是验证者,而不是错误搜寻工具;您将不得不对代码进行大量注释以使其验证,并且学习曲线可能有点陡峭。另请注意,它假设顺序一致性(与大多数工具一样)。

BTW,同行评审不是验证并发算法(甚至是顺序算法)的好方法。着名人士在重要期刊上发布并发算法的历史悠久,只是发现多年后发现的错误。