【问题标题】:Guaranteeing the order of execution without using volatile or memory barrier and locks在不使用易失性或内存屏障和锁的情况下保证执行顺序
【发布时间】:2017-07-31 12:04:21
【问题描述】:

我有关于编译器更改执行顺序的问题。我试图通过用信号机制(通过信号量)替换关键部分来提高多线程程序(C 语言)的性能。

这里我需要保证执行的顺序,并且一直在做一些研究。我看到很多关于函数内执行顺序的问题,但没有太多关于函数内函数的讨论。

基于https://en.wikipedia.org/wiki/Sequence_point 规则#4,下面的代码块是否保证必须在输入func2 之前首先评估*p->a,因为func2p 作为输入(假设编译器遵守此处定义的调度点规则)?

func1 (struct *p) {
  p->a = x;  
  func2 (p);
}

func2 (struct *p) {
  p->b = y;
  releaseSemaphore(s);
}

仅在设置p->a 后设置p->b 至关重要,因为另一个线程正在循环处理各种请求并通过是否设置p->b 来识别有效请求。释放信号量只有在空闲时触发任务(并等待信号量),但如果它正忙于处理其他请求,它会稍后检查p->b,我们不能保证只有在该线程空闲时才调用func1闲置的。

【问题讨论】:

  • 因为 p 不是原子的,所以:有效请求取决于是否设置了 p->b。 看起来很像数据竞赛。
  • 如果您的编译器支持 C11,您可以使用原子,例如:线程 1 在 p->b 上执行存储释放,线程 2 在 p->b 上执行加载获取。

标签: c multithreading performance compiler-optimization


【解决方案1】:

没有。序列点排序不会跨越线程边界。这就是为什么我们首先需要内存排序保证的全部原因。

对于执行代码的线程,序列点的顺序始终得到保证(模似规则)。任何 other 线程都可能以任意顺序观察该线程的写入。这意味着即使线程#1 可以验证它是否以特定顺序执行写入,线程#2 仍可能以不同的顺序观察它们。这就是为什么 volatile 在这里还不够。

从技术上讲,这可以解释为例如。通过缓存。线程#1 的写入可能首先进入写入缓冲区,线程#2 仍然看不到它们。只有在写入缓冲区被刷新回主内存后,它们才变得可见,并且允许硬件在刷新之前对写入重新排序。

请注意,仅仅因为平台允许重新排序写入并不意味着它会。这是危险的部分。可以在一个平台上完美运行的代码在移植到另一个平台时可能会突然出现。使用正确的内存顺序可以保证代码在任何地方都能正常工作。

【讨论】:

  • 另外,“sequence point”一词已被弃用,取而代之的是更现代和更精确的“sequenced before”术语。
  • 谢谢,关于缓存的解释对说明这一点很有帮助。
【解决方案2】:

实现可以1更改顺序,只要这不是通过来自其他翻译单元的函数调用来完成的。

这种重新排序与多线程正交,即它在单线程和多线程程序中都完成。

如果函数 func2 与 func1 在同一个翻译单元中,则执行如下:

func1 (struct *p) 
{
    func2 (p);
    p->a = x;  
}

如果您想防止2 此类重新排序,请使用 volatile。 (请注意,这样做是为了防止上面提到的重新排序,而不是出于其他同步目的。您必须为此使用原子原语。)


1(引自:ISO/IEC 9899:201x 5.1.2.3 程序执行 10)
或者,实现可能会在每个翻译单元内执行各种优化,例如 只有在进行函数调用时,实际语义才会与抽象语义一致 翻译单元边界。

2(引自:ISO/IEC 9899:201x 6.7.3 Type qualifiers 7)
具有 volatile 限定类型的对象可以以不知道的方式修改 实施或有其他未知的副作用。因此,任何引用的表达式 对这样的对象,应严格按照抽象机的规则进行评估, 如 5.1.2.3 所述。此外,在每个序列点,最后存储在 对象应与抽象机器规定的一致,除非由 前面提到的未知因素。

【讨论】:

  • 感谢您深入了解同一翻译单元中的重新排序。每天学些新东西。可惜我只能给我一个答案:)
  • @DanZ 这种重新排序当然必须保持程序的可观察行为相同。在这种情况下可以这样做,因为编译器看到函数 func2 中只有成员 b 被修改,而成员 a 根本没有被访问。如果不是这种情况,如果在函数 func2 中也读取了成员 a,那么对成员 a 的写入不能移到所述函数下方。
猜你喜欢
  • 2018-02-28
  • 2016-12-09
  • 1970-01-01
  • 2019-11-09
  • 1970-01-01
  • 2010-12-19
  • 1970-01-01
  • 2017-01-17
  • 2016-05-02
相关资源
最近更新 更多