问题标签 [memory-barriers]

For questions regarding programming in ECMAScript (JavaScript/JS) and its various dialects/implementations (excluding ActionScript). Note JavaScript is NOT the same as Java! Please include all relevant tags on your question; e.g., [node.js], [jquery], [json], [reactjs], [angular], [ember.js], [vue.js], [typescript], [svelte], etc.

0 投票
1 回答
162 浏览

c++ - 抢先式多任务处理会干扰 C++11 发布获取语义吗?

线程是否有可能(理论上)acquire在一个 CPU 上执行,然后立即被抢占并在另一个acquire从未执行过的 CPU 上恢复(因此根据释放-获取语义从不同步)?

例如。考虑以下代码,它使用 C++11 原子和release-acquire内存排序来执行无锁线程安全初始化:

如果_initFlag.load(memory_order_acquire)返回 true,那么调用线程将知道 , 等的初始化值对_foo当前正在执行_bar的 CPU 是可见的(传播的)。但是如果线程在之后立即被抢占并移动到另一个 CPU 怎么办?

C++11 标准是否保证新 CPU 会同步?是否有任何实现或架构可能容易受到这种竞争条件的影响?

0 投票
1 回答
3148 浏览

c - 用 C 语言调试 Lamport's Bakery 算法的简化版

在尝试使用它解决更复杂的问题之前,我正在尝试在 C 中实现 Lamport's Bakery Algorithm 的简化版本。* 我所做的简化是锁仅由两个线程而不是 N 共享。

我设置了两个线程(通过 OpenMP 使事情变得简单),它们循环,试图在它们的关键部分增加一个共享计数器。如果一切都按计划进行,那么最终的计数器值应该等于迭代次数。但是,这里有一些示例输出:

嗬!有些东西坏了,但是什么?我的实现是相当教科书(供参考),所以也许我在滥用内存屏障?我是否忘记将某些内容标记为易失性?

我的代码:

要编译和运行(在 Linux 上),请执行以下操作:

  • 我打算将简单的 Bakery 锁链接成二叉树(锦标赛风格),以实现 N 个线程之间的互斥。
0 投票
1 回答
999 浏览

thread-safety - asio 隐式链和数据同步

当我阅读 asio 源代码时,我很好奇 asio 如何使线程之间的数据同步,甚至是隐式链。这些是asio中的代码:

io_service::运行

io_service::do_run_one

在其do_run_one中,互斥锁的解锁都在执行处理程序之前。如果存在隐式链,handler 不会并发执行,但问题是:线程 A 运行一个修改数据的处理程序,线程 B 运行下一个处理程序读取已被线程 A 修改的数据。没有互斥锁的保护,线程B如何看到线程A对数据所做的更改?在处理程序执行之前解锁互斥锁不会在线程访问处理程序访问的数据之间建立发生之前的关系。当我走得更远时,处理程序执行使用了一个名为fenced_block的东西:

这是什么?我知道栅栏似乎是 C++11 支持的同步原语,但这个栅栏完全是由 asio 本身编写的。这个 fenced_block 是否有助于完成数据同步的工作?

更新

在我google并阅读thisthis之后,asio确实使用内存栅栏原语来同步线程中的数据,这比解锁更快,直到处理程序执行完成(x86上的速度差异)。事实上,Java volatile 关键字是通过在 write 之后插入内存屏障来实现的,然后再读取这个变量来建立发生前的关系。

如果有人可以简单地描述 asio 内存栅栏实现或添加我错过或误解的内容,我会接受。

0 投票
1 回答
464 浏览

c# - Spinlock.exit 有或没有内存屏障

由于内存屏障对我来说是一个新概念,我试图了解它们,所以我编写了以下测试程序(C#):

问题是关于 Spinlock.Exit(true) 函数。true 表示发出内存屏障以立即将退出操作发布到其他线程。

当传递 false 时,不会发出内存屏障,然后代码运行速度几乎是两倍。

如果操作对象将包含线程之间的共享内存并且传递 false,它仍然是一个正确的程序吗?为什么内存屏障这么慢?

0 投票
2 回答
4561 浏览

c++ - 编译器屏障的目的是什么?

以下摘自Windows上的并发编程,第10章第528~529页,一个c++模板Double check实现

正如作者所说:

_WriteBarrier 是在实例化对象之后但在 m_pValue 字段中写入指向它的指针之前找到的。这是确保对象初始化中的写入永远不会延迟超过对 m_pValue 本身的写入所必需的。

由于 _WriteBarrier 是编译屏障,我认为编译器知道 LeaveCriticalSection 的语义是没有用的。编译可能会省略对 pValue 的写入,但永远不要优化以便在函数调用之前移动赋值,否则会违反程序语义。我相信 LeaveCriticalSection 有隐式的硬件围栏。因此,在分配给 m_pValue 之前的任何写入都将被同步。

另一方面,如果编译不知道 LeaveCriticalSection 的语义,则所有平台都需要 _WriteBarrier以防止编译将赋值移出临界区。

而对于_ReadBarrier,作者说

同样,我们在返回 m_value 之前需要一个 _ReadBarrier,以便调用 getValue 之后的加载不会重新排序以在调用之前发生。

首先,如果这个函数包含在一个库中,并且没有可用的源代码,编译器如何知道是否存在编译障碍?

其次,如果需要它会放置错误的位置,我认为我们需要将它放在 EnterCriticalSection 之后以表示获取围栏。与我上面写的类似,这取决于编译是否理解 EnterCriticalSection 的语义。

而且作者还说:

但是,我还要指出,X86、Intel64 和 AMD64 处理器都不需要栅栏。不幸的是,像 IA64 这样的弱处理器搅浑了水

正如我上面分析的那样,如果我们在某些平台上需要这些障碍,那么我们在所有平台都需要它们,因为这些障碍是编译障碍,它只是确保编译可以做正确的优化,以防万一他们不明白一些函数的语义。

如果我错了,请纠正我。

另一个问题,msvc 和 gcc 是否有任何参考来指出它们理解其同步语义的哪些函数?

更新1:根据答案(m_pValue 将在临界区之外访问),并从这里运行示例代码,我认为:

  1. 我认为作者在这里的意思是编译障碍以外的硬件围栏,请参阅MSDN的以下引用。
  2. 我相信硬件栅栏也有隐式编译障碍(禁用编译优化),但反之亦然(见这里,使用 cpu 栅栏不会看到任何重新排序,反之亦然)

Barrier 不是栅栏。应该注意的是,Barrier 会影响缓存中的所有内容。栅栏影响单个高速缓存行。

除非绝对必要,否则不应添加障碍。要使用栅栏,您可以选择 _Interlocked 内部函数之一。

正如作者所写:“ X86 Intel64 和 AMD64 处理器都不需要栅栏”,这是因为这些平台只允许存储加载重新排序。

还有一个问题,编译器是否理解调用 Enter/Leave 临界区的语义?如果没有,那么它可能会按照以下答案进行优化,这将导致不良行为。

谢谢

0 投票
2 回答
5703 浏览

linux - 如何使用 Linux 内核中的内存屏障

内核源码 Documentation/memory-barriers.txt 中有一个说明,像这样:

在没有干预的情况下,CPU 2 可能会以某种有效的随机顺序感知 CPU 1 上的事件,尽管 CPU 1 发出了写屏障:

我不明白,因为我们有一个写屏障,所以,任何存储都必须在执行 C = &B 时生效,这意味着 B 将等于 2。对于 CPU 2,B 在获得值时应该是 2 C,也就是&B,为什么它会认为B是7。我真的很困惑。

0 投票
1 回答
697 浏览

objective-c - 使用无锁读取实现线程安全的无效缓存?

我试图在脑海中推理出如何使用 API 为引用计数值实现线程安全缓存机制,大致如下:(注意:我使用的是 Objective-C 语法,但问题不是特定于语言的)

当有人请求-value时,如果它有一个现有的缓存值,它应该返回-retain/-autoreleased该值的一个版本。如果它没有值,或者该值无效,它应该使用在初始化时传入的生成块生成一个,然后它应该缓存该值以供将来读取,直到有人调用-invalidate.

假设我们不关心生成器块是否被多次调用(即第二个读取器到达,而第一个读取器在生成器块中),只要它返回的对象在发生这种情况时没有泄漏。第一次通过,非无等待的实现可能看起来像:

自然,这会导致糟糕的读取性能,因为并发读取是由锁序列化的。读取器/写入器锁改善了这一点,但在读取路径中仍然很慢。这里的性能目标是使缓存读取尽可能快(希望无锁)。如果我们必须计算一个新值,读取速度慢是可以的,-invalidate速度慢也是可以的。

所以......我试图找出一种方法来使读取锁定/等待免费。我的第一个想法(有缺陷 - 见下文)涉及添加一个失效计数器,其值是原子的、单调递增的并使用内存屏障读取。它看起来像这样:

但我已经在这里看到了问题。例如,[mValue retain]在读取路径中:我们需要保留值,但是在读取mValue和调用-retain另一个线程之间的时间可能会-invalidate导致值在保留时已被释放通话。所以这种方法不会按原样工作。也可能有其他问题。

有没有人已经完成了类似的工作并愿意分享?或者有一个指向野外类似事物的指针?

0 投票
3 回答
1781 浏览

c++ - 放松的原子规则有什么(轻微的)区别?

在看到 Herb Sutters关于“原子武器”的精彩演讲后,我对“轻松原子”示例感到有些困惑。

我认为C++ 内存模型中的原子(SC-DRF = Sequentially Consistent for Data Race Free)在加载/读取时执行“获取”。

我知道对于负载 [和商店],默认值是std::memory_order_seq_cst,因此两者是相同的:

到目前为止一切顺利,没有涉及轻松原子(在听完演讲后,我永远不会使用轻松的原子。永远。承诺。但是当有人问我时,我可能不得不解释......)。

但是为什么当我使用时它是“宽松”的语义

既然负载获取不是释放,为什么这与(1)and不同(2)这里真正放松的是什么?

我唯一能想到的就是我误解了load的意思是acquire。如果这是真的,并且默认值seq_cst意味着两者,那是否意味着一个完整的围栏 - 没有什么可以传递该指令,也不能传递?我必须误解了那部分。

[并且对称地存储释放]。

0 投票
3 回答
19622 浏览

c++ - C++“内存屏障”示例

我正在阅读关于 volatile 关键字的这个问题的答案:

https://stackoverflow.com/a/2485177/997112

该人说:

防止重新排序的解决方案是使用内存屏障,它向编译器和 CPU 都表明,在这一点上不能对内存访问进行重新排序。在我们的 volatile 变量访问周围放置这样的障碍可以确保即使是非 volatile 访问也不会在 volatile 中重新排序,从而允许我们编写线程安全的代码。

但是,内存屏障还确保在达到屏障时执行所有挂起的读/写操作,因此它有效地为我们提供了我们需要的一切,从而使 volatile 变得不必要。我们可以完全删除 volatile 限定符。

这个“内存屏障”是如何在 C++ 中实现的?

编辑:

有人可以给出一个简单的代码示例吗?

0 投票
0 回答
362 浏览

linux-kernel - 为什么 MONITOR 和 MWAIT 之间需要内存屏障?

仔细阅读Linux x86 idle loop,我注意到monitor和之间存在内存障碍mwait,我无法弄清楚为什么它是必要的。

smp_mb()是一个宏asm volatile("mfence":::"memory")

为什么这里有必要?我理解为什么需要编译器内存屏障,而不是硬件内存屏障。