【问题标题】:Can a multi-threaded program ever be deterministic?多线程程序可以是确定性的吗?
【发布时间】:2011-04-19 07:46:18
【问题描述】:

通常说多线程程序是不确定的,这意味着如果它崩溃,几乎不可能重新创建导致该情况的错误。人们永远不知道接下来会运行哪个线程,以及何时再次被抢占。

当然,这与操作系统线程调度算法有关,而且人们不知道接下来要运行哪个线程,以及它会有效运行多长时间。 程序执行顺序也有影响,等等……

但是,如果您有用于线程调度的算法,并且如果您可以知道什么线程正在运行,那么多线程程序是否会变得“确定性”,例如,您将能够重现崩溃?

【问题讨论】:

  • 可能在未来,有些机器不允许多线程编程,有一些库框架可以模拟多线程环境,然后你可以回溯并重现错误。

标签: multithreading non-deterministic


【解决方案1】:

了解算法实际上并不能让您预测什么时候会发生什么。程序或线程执行中发生的各种延迟取决于环境条件,例如:可用内存、交换、传入中断、其他繁忙任务等。

如果您要将多线程程序映射到顺序执行,并且您的线程本身的行为是确定性的,那么您的整个程序可能是确定性的,并且“并发”问题可以重现。当然,到那时它们将不再是并发问题。

如果您想了解更多信息,http://en.wikipedia.org/wiki/Process_calculus 非常有趣。

【讨论】:

  • 我写了一个从 C 到 CSP(一种 Process_calculus)的翻译器,用于 PAT 工具(www.comp.nus.edu.sg/~pat/)。有一些模型检查器可供您检查并发程序。
【解决方案2】:

我的意见是:技术上没有(但数学上是)。您可以编写确定性线程算法,但是在您可以将其视为非确定性的合理时间之后,很难预测应用程序的状态。

【讨论】:

  • 所以更好的说法是,你的回答是:“数学上,是的。实际上,不是。”
【解决方案3】:

有一些工具(正在开发中)会尝试以某种可预测的方式创建竞争条件,但这是关于前瞻性测试,而不是重建“野外错误”。

CHESS 就是一个例子。

【讨论】:

    【解决方案4】:

    可以在虚拟多线程机器上运行程序,其中每个线程的虚拟周期分配是通过一些完全确定的过程完成的,可能使用伪随机生成器(可以用一个常数作为种子)在每个程序运行之前)。另一种可能更有趣的可能性是拥有一个虚拟机,它可以在“飞溅”模式下运行线程(其中几乎任何他们接触​​的变量都会使其值对其他线程变得“未知”)和“清理”模式(其中具有已知操作数的操作结果对于其他线程是可见的和已知的)。我预计这种情况可能有点类似于硬件模拟:如果每个门的输出在其最小和最大传播时间之间被认为是“未知的”,但模拟无论如何都有效,这很好地表明了设计是稳健的,但是有许多有用的设计无法在这样的模拟中工作(这些状态基本上可以保证演变成有效的组合,尽管不能保证是哪一个)。尽管如此,这可能是一个有趣的探索途径,因为许多程序的大部分都可以编写为即使在“飞溅模式”VM 下也能正常工作。

    【讨论】:

      【解决方案5】:

      多线程程序中的许多崩溃与多线程本身(或相关的资源争用)无关。

      【讨论】:

      • 但我说的是那些与多线程有关的问题。
      【解决方案6】:

      我认为这是不可行的。为了强制执行特定的线程交错,我们需要在共享变量上放置锁,强制线程以特定顺序访问它们。这会导致性能严重下降。

      重放并发错误通常由记录和重放系统处理。由于记录如此大量的信息也会降低性能,因此最新的系统会进行部分日志记录,然后使用 SMT 解决来完成线程交错。我相信这类系统的最新进展是共生(发表在今年的 PLDI 会议上)。 Tou 可以在这个 URL 中找到开源实现:

      http://www.gsd.inesc-id.pt/~nmachado/software/Symbiosis_Tutorial.html

      【讨论】:

        【解决方案7】:

        这实际上是当今许多想要并行执行任务但又不时需要一些确定性的系统中的有效要求。

        例如,一家移动公司希望并行处理多个用户的订阅事件,但希望一次执行一个用户的事件。

        一种解决方案当然是编写所有内容以在单个线程上执行。另一种解决方案是确定性线程。我用 Java 编写了一个简单的库,可以用来实现我在上面示例中描述的行为。看看这个-https://github.com/mukulbansal93/deterministic-threading

        话虽如此,CPU 到线程或进程的实际分配掌握在操作系统手中。因此,每次运行同一程序时,线程可能会以不同的顺序获取 CPU 周期。因此,您无法按照线程分配 CPU 周期的顺序来实现确定性。但是,通过在线程之间有效地委派任务,以便将顺序任务分配给单个线程,您可以在整个任务执行中实现确定性。

        另外,回答您关于模拟碰撞的问题。所有现代 CPU 调度算法都没有饥饿。因此,每个线程都必然会获得有保证的 CPU 周期。现在,您的崩溃可能是由于在单个 CPU 上执行特定线程序列造成的。没有办法重新运行相同的执行顺序或者相同的 CPU 周期分配顺序。但是,如果您运行代码足够多次,现代 CPU 调度算法的无饥饿和墨菲定律的组合将帮助您模拟错误。

        PS,enough times 的定义相当模糊,取决于很多因素,例如整个程序所需的执行周期、线程数等。从数学上讲,这是一种计算模拟概率的粗略方法相同的执行序列导致的错误是在单个处理器上是-

        1/Number of ways to execute all atomic operations of all defined threads

        例如,具有 2 个线程和 2 个原子指令的程序可以在单个处理器上以 4 种不同的方式分配 CPU 周期。所以概率是 1/4。

        【讨论】:

          【解决方案8】:

          通常说多线程程序是不确定的,这意味着如果它崩溃,几乎不可能重新创建导致该条件的错误。

          我完全不同意这一点,肯定多线程程序是不确定的,但考虑到用户输入、消息泵、鼠标/键盘处理和许多其他因素,单线程程序也是如此。多线程程序通常会使重现错误更加困难,但绝对不是不可能的。无论出于何种原因,程序执行都不是完全随机的,存在某种可重复性(但不是可预测性),我通常可以在我的应用程序中相当快地重现多线程错误,但是我的应用程序中有很多冗长的日志记录,因为最终用户的操作。

          顺便说一句,如果您遇到崩溃,您是否也可以获取崩溃日志以及调用堆栈信息?这将极大地帮助调试过程。

          【讨论】:

          • “考虑到用户输入、消息泵、鼠标/键盘处理”——这些都相对容易以完全确定的方式模拟(回放)。线程调度,没那么多。
          猜你喜欢
          • 1970-01-01
          • 2012-02-15
          • 1970-01-01
          • 2011-05-08
          • 2013-03-08
          • 2010-11-18
          • 1970-01-01
          • 2018-06-09
          • 2014-12-18
          相关资源
          最近更新 更多