0

在 Java、C 和 C++ 中,即使在编译器优化之后,源代码是否保证在单个给定线程中逐行顺序执行?如果允许系统重新排序您的代码,似乎什么都不会起作用,但我似乎找不到任何文档来保证如果我在 Java 中有以下内容:

class MyClass{
String testString = "";

public MyClass(){

}

public void foo(){
    testString = "foo";
}
public void bar(){
    testString = "bar";
    testString += "r";
}
public String getTestString(){
    return testString;
}

}

class Main{
static void main(String[] args){
    MyClass testClass = new MyClass();
    testClass.foo();
    System.out.println(class.getTestString());
    testClass.bar();
    System.out.println(class.getTestString());
}
}

输出将永远是

"foo"
"barr"

绝不

"foo"
"rbar"

或者如果其中的方法调用和语句未按照源代码中指定的顺序执行,则可能出现的任何其他可能变化。

这个问题特别与 Java 有关,因为它使程序员对目标系统上的字节码编译器和 JIT 编译器或解释器将对您的代码做什么的控制权明显减少。对我来说主要的系统是 Android,我为它实现了我自己的信号量和互斥锁机制(例如,没有过多使用内置的 Java 并发机制,如“同步”和“易失”关键字),这更适合我的应用程序比 Java 提供的应用程序。然而,一位朋友警告我,由于 Java 从源代码到机器代码经历了多层次的转换,除非我使用 Java 的内置并发机制,否则无法保证我的信号量和锁定实现会按我的预期执行。这实际上归结为是否有一个特定的保证,对于任何给定的运行时实现,代码的执行将在单个线程中顺序执行。所以主要的问题是:

  1. 在 C 和 C++ 中,尽管编译器优化,代码执行是否保证是连续的?如果不是,禁用编译器优化是否足以实现这样的保证?

  2. 尽管字节码编译器和 JIT 编译器或解释器(特别是在 Android 上运行但也适用于任意 VM 实现)可能会进行更改,但在 Java 中是否保证代码执行是连续的?

  3. 如果上述答案如我所料是肯定的,是否有任何编程语言/平台/上下文不能保证在单个线程中顺序执行?

4

6 回答 6

3

如果只有一个线程,您的代码将具有直观预期的结果。任何优化都必须在优化之前保留功能。只有当你有多个线程时,才会发生意想不到的事情。

Java 语言规范基本上处理存在多个线程时出现的违反直觉的行为,您的问题是关于此处定义的“线程内语义”:

内存模型决定了程序中每个点可以读取哪些值。每个单独的线程的操作必须按照该线程的语义进行操作,但每次读取看到的值由内存模型确定。当我们提到这个时,我们说程序遵循线程内语义。线程内语义是单线程程序的语义,允许根据线程内读取操作看到的值来完整预测线程的行为。为了确定线程 t 在执行中的动作是否合法,我们只需评估线程 t 的实现,因为它将在单线程上下文中执行,如本规范的其余部分所定义。

http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html

于 2013-02-22T15:52:11.947 回答
2

最重要的是,JIT / 编译器更改指令以优化代码执行,使其不会更改单个给定线程内的代码执行结果。

在 Java 中,如果您将代码放在同步的方法/块中,则可以保证不会有指令重新排序。

您可以在这篇文章中找到更多答案http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#otherlanguages

于 2013-02-22T15:58:26.367 回答
2

即使在没有线程的情况下,大多数编译器也被允许重新排序代码以获得最佳速度。但不允许重新排序的结果影响结果。在 C/C++ 中,这称为 as-if 规则。

因此,只要结果不受影响,编译器就可以在函数内重新排序以进行优化。所以我们在“源代码”和“生成代码”之间存在细微差别。

在线程代码中,单个线程会执行一个函数吗?这取决于您如何定义线程。究竟什么是线程(一组堆栈帧/寄存器/和当前执行点)。

我认为在内核之间移动线程没有问题(尽管我可以看到为什么运行时不想这样做,但我不认为这是不可能的)。所以你不能假设线程不会跳核。但是,如果您认为“执行线程”是当前状态而不参考任何硬件。

您可以保证的是,“执行线程”将从一段“生成的代码”的开始到结束执行。它不会遗漏任何“生成的代码”。它将按顺序执行“生成的代码”。如果它是未安排的和重新安排的,它将完全从它离开的地方继续。

于 2013-02-22T16:24:25.820 回答
1

是的,您上面提供的代码将按顺序执行。即使有 50 个处理器,您的代码也会在一个线程上按顺序执行。

顺便说一句,很确定你不能调用变量类。即使可以,也不要。

于 2013-02-22T15:52:22.457 回答
1

您的示例将按顺序执行,但如果操作数和其他表达式发挥作用,它会比这更复杂。此处描述了一般规则:http: //docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.7

请注意关于特定表达式的评估顺序的最后一部分:http: //docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.7.5

于 2013-02-22T15:57:52.263 回答
1

我认为简单的答案是编译器开发人员遵循“你不应该修改单线程程序的行为”的口头禅,除此之外我认为你是靠自己的。

“preshing on programming”对我见过的乱序执行问题有一些最好的解释。如果有人知道一组更好的文章,我会知道的。这是其中两个,但如果您真的想深入研究它,您应该在网站上查看更多材料:

编译时的内存排序
弱与强内存模型

于 2013-02-22T16:49:38.837 回答