C:线程安全和操作顺序

时间:2018-11-21 15:10:19

标签: c thread-safety

考虑以下C代码:

static sig_atomic_t x;
static sig_atomic_t y;

int foo()
{
    x = 1;
    y = 2;
}

第一个问题:C编译器是否可以决定将foo的代码“优化”到y = 2; x = 1(从某种意义上说,y的存储位置在更改之前的位置) x)?这是等效的,除非涉及多个线程或信号。

如果第一个问题的答案是“是”:如果我真的想保证将x存储在y之前该怎么办?

2 个答案:

答案 0 :(得分:1)

是的,编译器可以更改两个分配的顺序,因为重新排序不是C标准所定义的“可观察的” ,例如,分配没有副作用(再次是C标准所定义的,它不考虑外部观察者的存在。

实际上,您需要某种屏障/栅栏来保证顺序,例如,使用多线程环境提供的服务,或者使用C11 stdatomic.h(如果可用)提供的服务。

答案 1 :(得分:0)

C标准指定了一个称为可观察到的行为的术语。这意味着编译器/系统至少有一些限制:不允许对包含volatile限定操作数的表达式进行重新排序,也不允许对输入/输出进行重新排序。

除那些特殊情况外,任何事情都是公平的。它可以在x之前执行y,也可以并行执行它们。由于代码中没有可观察到的副作用,因此可能会优化整个代码。依此类推。

请注意,线程安全性和执行顺序是不同的。线程是由程序员/库显式创建的。上下文切换可以中断任何非原子的变量访问。那是另一个问题,解决方案是使用互斥,_Atomic限定符或类似的保护机制。


如果顺序很重要,则应volatile限定变量。在这种情况下,该语言将提供以下保证:

C17 5.1.2.3§6(可观察到的行为的定义):

  

严格根据抽象机的规则评估对易失对象的访问。

C17 5.1.2.3§4:

  

在抽象机中,所有表达式均按语义指定的方式求值。

其中“语义”几乎是整个标准,例如,指定;的部分由序列点组成。 (在这种情况下,C17 6.7.6“ 声明者是一个序列点。”术语“先于序列”在C17 5.1.2.3§3)中指定。

因此,此:

volatile int x = 1;
volatile int y = 1;

然后确保初始化顺序为y之前的x,因为第一行的;保证了排序顺序,而volatile保证程序严格遵循标准。


现在volatile不能在现实世界中发生,因此不能保证在多核系统的许多编译器实现中的内存障碍。这些实现不符合要求。

机会主义者的编译器可能声称程序员必须使用特定于系统的内存屏障来保证执行顺序。但是,对于volatile,这是不正确的,如上所述。他们只是想回避自己的责任,并将其移交给程序员。 C标准不关心CPU是否具有57个内核,分支预测和指令流水线。