繁体   English   中英

序列点是否会阻止代码在关键部分边界重新排序?

[英]Do sequence points prevent code reordering across critical section boundaries?

假设有一个基于锁的代码,如下所示,其中互斥体用于防止不适当的并发读写

mutex.get() ; // get a lock.

T localVar = pSharedMem->v ; // read something
pSharedMem->w = blah ; // write something.
pSharedMem->z++ ;      // read and write something.

mutex.release() ; // release the lock.

如果假设生成的代码是按程序顺序创建的,则仍然需要适当的硬件内存屏障,如isync,lwsync,.acq,.rel。 我假设这个问题是互斥实现负责这个部分,提供一个保证pSharedMem读取和写入都发生在“get”之后,“之前”发布()[但周围的读取和写入可以进入关键部分,因为我期望是互斥实现的标准]。 我还假设在适当的情况下在互斥体实现中使用volatile访问,但是volatile不用于受互斥体保护的数据(理解为什么volatile似乎不是受互斥保护的数据的必要数据实际上是这个问题)。

我想了解是什么阻止编译器移动关键区域之外的pSharedMem访问。 在C和C ++标准中,我看到有一个序列点的概念。 我发现标准文档中的大部分序列点文本都是不可理解的,但如果我要猜测它是什么,那么声明代码不应该在有未知副作用的调用的点上重新排序。 这是它的主旨吗? 如果是这种情况,编译器在这里有什么样的优化自由度?

随着编译器进行棘手的优化,例如配置文件驱动的过程间内联(甚至跨文件边界),甚至未知副作用的概念也会变得模糊。

在这里以自包含的方式解释这个问题可能超出了一个简单问题的范围,因此我很容易被指向引用(最好是在线并且针对凡人程序员而不是编译器编写者和语言设计者)。

编辑:(回应Jalf的回复)

我提到了像lwsync和isync这样的内存屏障指令,因为你还提到了CPU重新排序问题。 我碰巧在与编译器工作者相同的实验室工作(至少对于我们的平台之一),并且已经与内在函数的实现者交谈过,我碰巧知道至少对于xlC编译器__isync()和__lwsync()(其余的原子内在函数)也是代码重新排序的障碍。 在我们的自旋锁实现中,编译器可以看到这一点,因为我们的关键部分的这一部分是内联的。

但是,假设您没有使用自定义构建锁实现(就像我们碰巧那样,这可能不常见),并且只调用了一个通用接口,例如pthread_mutex_lock()。 在那里,编译器不会通知原型。 我从未见过它表明代码不起作用

pthread_mutex_lock( &m ) ;
pSharedMem->someNonVolatileVar++ ;
pthread_mutex_unlock( &m ) ;

pthread_mutex_lock( &m ) ;
pSharedMem->someNonVolatileVar++ ;
pthread_mutex_unlock( &m ) ;

除非将变量更改为volatile,否则将无法正常工作。 该增量将在每个背靠背代码块中具有加载/增量/存储序列,并且如果第一增量的值在第二个寄存器中保留,则将不能正确地操作。

似乎pthread_mutex_lock()的未知副作用可以保护这种背靠背增量示例不正常行为。

我在谈论自己的结论,在线程环境中这样的代码序列的语义并没有真正严格地涵盖在C或C ++语言规范中。

简而言之,只要C ++虚拟机上的可观察行为没有改变,就允许编译器根据需要重新排序或转换程序。 C ++标准没有线程概念,因此这个虚构的VM只运行一个线程。 在这样一个假想的机器上,我们不必担心其他线程看到的内容。 只要更改不改变当前线程的结果,所有代码转换都是有效的,包括重新排序跨序列点的内存访问。

理解为什么volatile似乎不是受互斥保护的数据的要求数据实际上是这个问题的一部分

Volatile确保一件事,只有一件事:每次都会从内存中读取一个volatile变量的读取 - 编译器不会认为该值可以缓存在寄存器中。 同样,写入将写入内存。 编译器不会将它保留在寄存器中“暂时将其写入存储器”。

但就是这样。 当发生写入时,将执行写入,并且当发生读取时,将执行读取。 但它并不保证何时会发生这种读/写操作。 正如通常那样,编译器可以按照它认为合适的方式对操作进行重新排序(只要它不会改变当前线程中的可观察行为,即虚构的C ++ CPU所知道的行为)。 所以挥发性并不能真正解决问题。 另一方面,它提供了我们并不真正需要的保证。 我们不需要立即写出对变量的每次写入,我们只是想确保在跨越边界之前将它们写出来。 如果它们在那之前被缓存就好了 - 同样,一旦我们越过临界区边界,后续的写入可以再次缓存我们关心的所有 - 直到我们下次越过边界。 如此动荡提供,我们并不需要太强有力的保证,但不提供一个我们确实需要(即读/写操作将不会重新排序)

因此,要实现关键部分,我们需要依赖编译器魔术。 我们必须告诉它“好吧,暂时忘记C ++标准,如果你严格遵循它,我不关心它会允许哪些优化。你不能重新排序跨越这个边界的任何内存访问”。

关键部分通常通过特殊的编译器内在函数(基本上是编译器可以理解的特殊函数)实现,1)强制编译器避免重新排序该内部函数,2)使其发出必要的指令以使CPU尊重相同的边界(因为CPU也重新排序指令,并且没有发出内存屏障指令,我们冒险CPU执行相同的重新排序,我们只是阻止编译器这样做)

不,序列点不会阻止重新排列操作。 控制优化的主要,最广泛的规则是强加于所谓的可观察行为的要求。 根据定义,可观察行为是对volatile变量的读/写访问以及对库I / O函数的调用。 这些事件必须以相同的顺序发生,并产生与“规范”执行程序中相同的结果。 其他所有内容都可以由编译器完全自由地重新排列和优化,以任何方式认为合适,完全忽略序列点所施加的任何排序。

当然,大多数编译器都试图不做任何过度的重新排列。 但是,近年来,您提到的问题已成为现代编译器的一个实际问题。 许多实现提供了额外的特定于实现的机制,允许用户在进行优化重新排列时要求编译器不要跨越某些边界。

正如您所说,由于受保护的数据未被声明为volatile ,因此正式地说,访问权限可以移到受保护区域之外。 如果将数据声明为volatile ,则应防止这种情况发生(假设互斥访问也是volatile )。

让我们看看下面的例子:

my_pthread_mutex_lock( &m ) ;
someNonVolatileGlobalVar++ ;
my_pthread_mutex_unlock( &m ) ;

函数my_pthread_mutex_lock()只是调用pthread_mutex_lock()。 通过使用my_pthread_mutex_lock(),我确信编译器不知道它是一个同步函数。 对于编译器来说,它只是一个函数,对我来说,它是一个可以轻松重新实现的同步函数。 因为someNonVolatileGlobalVar是全局的,所以我预计编译器不会在关键部分之外移动someNonVolatileGlobalVar ++。 实际上,由于可观察的行为 ,即使在单线程情况下,编译器也不知道之前的函数和该指令之后的函数是否正在修改全局变量。 因此,为了保持可观察行为的正确性,它必须在写入时保持执行顺序。 我希望pthread_mutex_lock()和pthread_mutex_unlock()也执行硬件内存屏障,以防止硬件将此指令移出临界区。

我是对的吗?

如果我写:

my_pthread_mutex_lock( &m ) ;
someNonVolatileGlobalVar1++ ;
someNonVolatileGlobalVar2++ ;
my_pthread_mutex_unlock( &m ) ;

我不知道两个变量中的哪一个首先递增,但这通常不是问题。

现在,如果我写:

someGlobalPointer = &someNonVolatileLocalVar;
my_pthread_mutex_lock( &m ) ;
someNonVolatileLocalVar++ ;
my_pthread_mutex_unlock( &m ) ;

要么

someLocalPointer = &someNonVolatileGlobalVar;
my_pthread_mutex_lock( &m ) ;
(*someLocalPointer)++ ;
my_pthread_mutex_unlock( &m ) ;

编译器是否正在做一个真正的开发人员期望的东西?

例如,当';'时出现C / C ++序列点 遇到了。 此时必须发生之前所有操作的所有副作用。 但是,我很确定通过“副作用”意味着操作是语言本身的一部分(比如z在'z ++'中递增)而不是在更低/更高级别的效果(就像操作系统实际上做的那样)关于操作完成后的内存管理,线程管理等)。

那回答你的问题吗? 我的观点实际上只是AFAIK序列点的概念与你所指的副作用并没有任何关系。

心连心

看一下[linux-kernel] /Documentation/memory-barriers.txt中的内容

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM