562 lines
83 KiB
HTML
562 lines
83 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh" dir="ltr" class="client-nojs" xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh">
|
|
<head><meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
<title>std::memory_order</title>
|
|
<link rel="stylesheet" href="ext.css" />
|
|
<link rel="stylesheet" href="site_modules.css" />
|
|
</head>
|
|
<body class="mediawiki ltr sitedir-ltr ns-0 ns-subject page-cpp_atomic_memory_order skin-cppreference2 action-view cpp-navbar">
|
|
<div id="cpp-content-base">
|
|
<div id="content"><a id="top"></a>
|
|
<h1 id="firstHeading" class="firstHeading"><span style="font-size:0.7em; line-height:130%">std::</span>memory_order</h1>
|
|
<div id="bodyContent">
|
|
<div id="contentSub"><span class="subpages">< <a href="cpp.html">cpp</a>‎ | <a href="cpp-atomic.html">atomic</a></span></div>
|
|
<div id="mw-content-text" lang="zh" dir="ltr" class="mw-content-ltr" xml:lang="zh">
|
|
<table class="t-dcl-begin">
|
|
<tbody>
|
|
<tr class="t-dsc-header">
|
|
<td>
|
|
<div>定义于头文件 <code><a href="cpp-header-atomic.html"><atomic></a></code></div>
|
|
</td>
|
|
<td></td>
|
|
<td></td>
|
|
</tr>
|
|
</tbody>
|
|
<tbody class="t-dcl-rev">
|
|
<tr class="t-dcl t-since-cxx11 t-until-cxx20">
|
|
<td>
|
|
<div><span class="mw-geshi cpp source-cpp"><span class="kw1">typedef</span> <span class="kw2">enum</span> memory_order <span class="br0">{</span><br /></span>
|
|
<p> memory_order_relaxed,<br />
|
|
memory_order_consume,<br />
|
|
memory_order_acquire,<br />
|
|
memory_order_release,<br />
|
|
memory_order_acq_rel,<br />
|
|
memory_order_seq_cst<br /></p>
|
|
<span class="br0">}</span> memory_order<span class="sy4">;</span></div>
|
|
</td>
|
|
<td class="t-dcl-nopad"></td>
|
|
<td><span class="t-mark-rev t-since-cxx11">(C++11 起)</span><br />
|
|
<span class="t-mark-rev t-until-cxx20">(C++20 前)</span></td>
|
|
</tr>
|
|
<tr class="t-dcl t-since-cxx20">
|
|
<td>
|
|
<div><span class="mw-geshi cpp source-cpp"><span class="kw2">enum</span> <span class="kw1">class</span> memory_order <span class="sy4">:</span> <span class="coMULTI">/*unspecified*/</span> <span class="br0">{</span><br /></span>
|
|
<p> relaxed, consume, acquire, release, acq_rel, seq_cst<br />
|
|
<span class="br0">}</span><span class="sy4">;</span><br />
|
|
<span class="kw1">inline</span> <span class="kw4">constexpr</span> memory_order memory_order_relaxed <span class="sy1">=</span> memory_order<span class="sy4">::</span><span class="me2">relaxed</span><span class="sy4">;</span><br />
|
|
<span class="kw1">inline</span> <span class="kw4">constexpr</span> memory_order memory_order_consume <span class="sy1">=</span> memory_order<span class="sy4">::</span><span class="me2">consume</span><span class="sy4">;</span><br />
|
|
<span class="kw1">inline</span> <span class="kw4">constexpr</span> memory_order memory_order_acquire <span class="sy1">=</span> memory_order<span class="sy4">::</span><span class="me2">acquire</span><span class="sy4">;</span><br />
|
|
<span class="kw1">inline</span> <span class="kw4">constexpr</span> memory_order memory_order_release <span class="sy1">=</span> memory_order<span class="sy4">::</span><span class="me2">release</span><span class="sy4">;</span><br />
|
|
<span class="kw1">inline</span> <span class="kw4">constexpr</span> memory_order memory_order_acq_rel <span class="sy1">=</span> memory_order<span class="sy4">::</span><span class="me2">acq_rel</span><span class="sy4">;</span><br /></p>
|
|
<span class="kw1">inline</span> <span class="kw4">constexpr</span> memory_order memory_order_seq_cst <span class="sy1">=</span> memory_order<span class="sy4">::</span><span class="me2">seq_cst</span><span class="sy4">;</span></div>
|
|
</td>
|
|
<td class="t-dcl-nopad"></td>
|
|
<td><span class="t-mark-rev t-since-cxx20">(C++20 起)</span></td>
|
|
</tr>
|
|
</tbody>
|
|
<tbody>
|
|
<tr class="t-dcl-sep">
|
|
<td></td>
|
|
<td></td>
|
|
<td></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<p><code>std::memory_order</code> 指定内存访问,包括常规的非原子内存访问,如何围绕原子操作排序。在没有任何制约的多处理器系统上,多个线程同时读或写数个变量时,一个线程能观测到变量值更改的顺序不同于另一个线程写它们的顺序。其实,更改的顺序甚至能在多个读取线程间相异。一些类似的效果还能在单处理器系统上出现,因为内存模型允许编译器变换。</p>
|
|
<p>库中所有原子操作的默认行为提供<i>序列一致顺序</i>(见后述讨论)。该默认行为可能有损性能,不过可以给予库的原子操作额外的 <code>std::memory_order</code> 参数,以指定附加制约,在原子性外,编译器和处理器还必须强制该操作。</p>
|
|
<h3><span class="mw-headline" id=".E5.B8.B8.E9.87.8F">常量</span></h3>
|
|
<table class="t-dsc-begin">
|
|
<tr class="t-dsc-header">
|
|
<td colspan="2">
|
|
<div>定义于头文件 <code><a href="cpp-header-atomic.html"><atomic></a></code></div>
|
|
</td>
|
|
</tr>
|
|
<tr class="t-dsc-hitem">
|
|
<td>值</td>
|
|
<td>解释</td>
|
|
</tr>
|
|
<tr class="t-dsc">
|
|
<td><code>memory_order_relaxed</code></td>
|
|
<td>宽松操作:没有同步或顺序制约,仅对此操作要求原子性(见下方<a href="cpp-atomic-memory_order.html#.E5.AE.BD.E6.9D.BE.E9.A1.BA.E5.BA.8F">宽松顺序</a>)。</td>
|
|
</tr>
|
|
<tr class="t-dsc">
|
|
<td><code>memory_order_consume</code></td>
|
|
<td>有此内存顺序的加载操作,在其影响的内存位置进行<i>消费操作</i>:当前线程中依赖于当前加载的该值的读或写不能被重排到此加载前。其他释放同一原子变量的线程的对数据依赖变量的写入,为当前线程所可见。在大多数平台上,这只影响到编译器优化(见下方<a href="cpp-atomic-memory_order.html#.E9.87.8A.E6.94.BE.E6.B6.88.E8.B4.B9.E9.A1.BA.E5.BA.8F">释放消费顺序</a>)。</td>
|
|
</tr>
|
|
<tr class="t-dsc">
|
|
<td><code>memory_order_acquire</code></td>
|
|
<td>有此内存顺序的加载操作,在其影响的内存位置进行<i>获得操作</i>:当前线程中读或写不能被重排到此加载前。其他释放同一原子变量的线程的所有写入,能为当前线程所见(见下方<a href="cpp-atomic-memory_order.html#.E9.87.8A.E6.94.BE.E8.8E.B7.E5.BE.97.E9.A1.BA.E5.BA.8F">释放获得顺序</a>)。</td>
|
|
</tr>
|
|
<tr class="t-dsc">
|
|
<td><code>memory_order_release</code></td>
|
|
<td>有此内存顺序的存储操作进行<i>释放操作</i>:当前线程中的读或写不能被重排到此存储后。当前线程的所有写入,可见于获得该同一原子变量的其他线程(见下方<a href="cpp-atomic-memory_order.html#.E9.87.8A.E6.94.BE.E8.8E.B7.E5.BE.97.E9.A1.BA.E5.BA.8F">释放获得顺序</a>),并且对该原子变量的带依赖写入变得对于其他消费同一原子对象的线程可见(见下方<a href="cpp-atomic-memory_order.html#.E9.87.8A.E6.94.BE.E6.B6.88.E8.B4.B9.E9.A1.BA.E5.BA.8F">释放消费顺序</a>)。</td>
|
|
</tr>
|
|
<tr class="t-dsc">
|
|
<td><code>memory_order_acq_rel</code></td>
|
|
<td>带此内存顺序的读修改写操作既是<i>获得操作</i>又是<i>释放操作</i>。当前线程的读或写内存不能被重排到此存储前或后。所有释放同一原子变量的线程的写入可见于修改之前,而且修改可见于其他获得同一原子变量的线程。</td>
|
|
</tr>
|
|
<tr class="t-dsc">
|
|
<td><code>memory_order_seq_cst</code></td>
|
|
<td>有此内存顺序的加载操作进行<i>获得操作</i>,存储操作进行<i>释放操作</i>,而读修改写操作进行<i>获得操作</i>和<i>释放操作</i>,再加上存在一个单独全序,其中所有线程以同一顺序观测到所有修改(见下方<a href="cpp-atomic-memory_order.html#.E5.BA.8F.E5.88.97.E4.B8.80.E8.87.B4.E9.A1.BA.E5.BA.8F">序列一致顺序</a>)。</td>
|
|
</tr>
|
|
</table>
|
|
<h3><span class="mw-headline" id=".E6.AD.A3.E5.BC.8F.E6.8F.8F.E8.BF.B0">正式描述</span></h3>
|
|
<p>线程间同步和内存顺序决定表达式的<i>求值</i>和<i>副效应</i>如何在不同的执行线程间排序。它们用下列术语定义:</p>
|
|
<h4><span class="mw-headline" id=".E5.85.88.E5.BA.8F.E4.BA.8E">先序于</span></h4>
|
|
<p>在同一线程中,求值 A 可以<i>先序于</i>求值 B ,如<a href="cpp-language-eval_order.html">求值顺序</a>中所描述。</p>
|
|
<h4><span class="mw-headline" id=".E6.90.BA.E5.B8.A6.E4.BE.9D.E8.B5.96">携带依赖</span></h4>
|
|
<p>在同一线程中,若下列任一为真,则<i>先序于</i>求值 B 的求值 A 可能也会将依赖带入 B (即 B 依赖于 A )</p>
|
|
<div class="t-li1"><span class="t-li">1)</span> A 的值被用作 B 的运算数,<b>除了</b></div>
|
|
<div class="t-li2"><span class="t-li">a)</span> B 是对 <span class="t-lc"><a href="cpp-atomic-kill_dependency.html">std::kill_dependency</a></span> 的调用</div>
|
|
<div class="t-li2"><span class="t-li">b)</span> A是内建 <span class="t-c"><span class="mw-geshi cpp source-cpp"><span class="sy3">&&</span></span></span> 、 <span class="t-c"><span class="mw-geshi cpp source-cpp"><span class="sy3">||</span></span></span> 、 <span class="t-c"><span class="mw-geshi cpp source-cpp"><span class="sy4">?:</span></span></span> 或 <span class="t-c"><span class="mw-geshi cpp source-cpp">,</span></span> 运算符的左运算数。</div>
|
|
<div class="t-li1"><span class="t-li">2)</span> A 写入标量对象 M,B 从 M 读取</div>
|
|
<div class="t-li1"><span class="t-li">3)</span> A 将依赖携带入另一求值 X ,而 X 将依赖携带入 B</div>
|
|
<h4><span class="mw-headline" id=".E4.BF.AE.E6.94.B9.E9.A1.BA.E5.BA.8F">修改顺序</span></h4>
|
|
<p>对任何特定的原子变量的修改,以限定于此一原子变量的单独全序出现。</p>
|
|
<p>对所有原子操作保证下列四个要求:</p>
|
|
<div class="t-li1"><span class="t-li">1)</span> <b>写写连贯</b>:若修改某原子对象 M 的求值 A (写操作)<i>先发生于</i>修改 M 的求值 B ,则 A 在 M 的<i>修改顺序</i>中早于 B 出现。</div>
|
|
<div class="t-li1"><span class="t-li">2)</span> <b>读读连贯</b>:若某原子对象 M 的值计算 A (读操作)<i>先发生于</i>对 M 的值计算 B ,且 A 的值来自对 M 的写操作 X ,则 B 的值要么是 X 所存储的值,要么是在 M 的<i>修改顺序</i>中后于 X 出现的 M 上的副效应 Y 所存储的值。</div>
|
|
<div class="t-li1"><span class="t-li">3)</span> <b>读写连贯</b>:若某原子对象 M 的值计算 A (读操作)<i>先发生于</i> M 上的操作 B (写操作),则 A 的值来自 M 的<i>修改顺序</i>中早于 B 出现的副效应 X (写操作)。</div>
|
|
<div class="t-li1"><span class="t-li">4)</span> <b>写读连贯</b>:若原子对象 M 上的副效应 X (写操作)<i>先发生于</i> M 的值计算 B (读操作),则求值 B 应从 X 或从 M 的<i>修改顺序</i>中后随 X 的副效应 Y 取得其值。</div>
|
|
<h4><span class="mw-headline" id=".E9.87.8A.E6.94.BE.E5.BA.8F.E5.88.97">释放序列</span></h4>
|
|
<p>在原子对象 M 上执行一次<i>释放操作</i> A 之后, M 的修改顺序的最长连续子序列由下列内容组成</p>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-until-cxx20">
|
|
<td>
|
|
<div class="t-li1"><span class="t-li">1)</span> 由执行 A 的同一线程所执行的写操作</div>
|
|
</td>
|
|
<td><span class="t-mark-rev t-until-cxx20">(C++20 前)</span></td>
|
|
</tr>
|
|
</table>
|
|
<div class="t-li1"><span class="t-li">2)</span> 任何线程对 M 的原子的读-修改-写操作</div>
|
|
<p>被称为<i>以 A 为首的释放序列</i>。</p>
|
|
<h4><span class="mw-headline" id=".E4.BE.9D.E8.B5.96.E5.85.88.E5.BA.8F.E4.BA.8E">依赖先序于</span></h4>
|
|
<p>在线程间,若下列任一为真,则求值 A <i>依赖先序于</i>求值 B</p>
|
|
<div class="t-li1"><span class="t-li">1)</span> A 在某原子对象 M 上进行<i>释放操作</i>,而不同的线程中, B 在同一原子对象 M 上进行<i>消费操作</i>,而 B 读取 A <span class="t-rev-inl t-until-cxx20"><span>所引领的释放序列的任何部分</span> <span><span class="t-mark-rev t-until-cxx20">(C++20 前)</span></span></span>所写入的值。</div>
|
|
<div class="t-li1"><span class="t-li">2)</span> A 依赖先序于 X 且 X 携带依赖到 B 。</div>
|
|
<h4><span class="mw-headline" id=".E7.BA.BF.E7.A8.8B.E9.97.B4.E5.85.88.E5.8F.91.E7.94.9F.E4.BA.8E">线程间先发生于</span></h4>
|
|
<p>在线程间,若下列任一为真,则求值 A <i>线程间先发生于</i>求值 B</p>
|
|
<div class="t-li1"><span class="t-li">1)</span> A <i>同步于</i> B</div>
|
|
<div class="t-li1"><span class="t-li">2)</span> A <i>依赖先序于</i> B</div>
|
|
<div class="t-li1"><span class="t-li">3)</span> A <i>同步于</i>某求值 X ,而 X <i>先序于</i> B</div>
|
|
<div class="t-li1"><span class="t-li">4)</span> A <i>先序于</i>某求值 X ,而 X <i>线程间先发生于</i> B</div>
|
|
<div class="t-li1"><span class="t-li">5)</span> A <i>线程间先发生于</i>某求值 X ,而 X <i>线程间先发生于</i> B</div>
|
|
<h4><span class="mw-headline" id=".E5.85.88.E5.8F.91.E7.94.9F.E4.BA.8E">先发生于</span></h4>
|
|
<p>无关乎线程,若下列任一为真,则求值 A <i>先发生于</i>求值 B :</p>
|
|
<div class="t-li1"><span class="t-li">1)</span> A <i>先序于</i> B</div>
|
|
<div class="t-li1"><span class="t-li">2)</span> A <i>线程间先发生于</i> B</div>
|
|
<p>要求实现确保<i>先发生于</i>关系是非循环的,若有必要则引入额外的同步(若引入消费操作,它才可能为必要,见 <a rel="nofollow" class="external text" href="http://www.cl.cam.ac.uk/~pes20/cpp/popl085ap-sewell.pdf">Batty 等</a>)。</p>
|
|
<p>若一次求值修改一个内存位置,而其他求值读或修改同一内存位置,且至少一个求值不是原子操作,则程序的行为未定义(程序有<a href="cpp-language-memory_model.html">数据竞争</a>),除非这两个求值之间存在<i>先发生于</i>关系。</p>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-since-cxx20">
|
|
<td>
|
|
<h4><span class="mw-headline" id=".E7.AE.80.E5.8D.95.E5.85.88.E5.8F.91.E7.94.9F.E4.BA.8E">简单先发生于</span></h4>
|
|
<p>无关乎线程,若下列之一为真,则求值 A <i>简单先发生于</i>求值 B :</p>
|
|
<div class="t-li1"><span class="t-li">1)</span> A <i>先序于</i> B</div>
|
|
<div class="t-li1"><span class="t-li">2)</span> A <i>同步于</i> B</div>
|
|
<div class="t-li1"><span class="t-li">3)</span> A <i>简单先发生于</i> X ,而 X <i>简单先发生于</i> B</div>
|
|
<p>注:不计消费操作,则<i>简单先发生于</i>与<i>强先发生于</i>关系是相同的。</p>
|
|
</td>
|
|
<td><span class="t-mark-rev t-since-cxx20">(C++20 起)</span></td>
|
|
</tr>
|
|
</table>
|
|
<h4><span class="mw-headline" id=".E5.BC.BA.E5.85.88.E5.8F.91.E7.94.9F.E4.BA.8E">强先发生于</span></h4>
|
|
<p>无关乎线程,若下列之一为真,则求值 A <i>强先发生于</i>求值 B :</p>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-until-cxx20">
|
|
<td>
|
|
<div class="t-li1"><span class="t-li">1)</span> A <i>先序于</i> B</div>
|
|
<div class="t-li1"><span class="t-li">2)</span> A <i>同步于</i> B</div>
|
|
<div class="t-li1"><span class="t-li">3)</span> A <i>强先发生于</i> X ,而 X <i>强先发生于</i> B</div>
|
|
</td>
|
|
<td><span class="t-mark-rev t-until-cxx20">(C++20 前)</span></td>
|
|
</tr>
|
|
<tr class="t-rev t-since-cxx20">
|
|
<td>
|
|
<div class="t-li1"><span class="t-li">1)</span> A <i>先序于</i> B</div>
|
|
<div class="t-li1"><span class="t-li">2)</span> A <i>同步于</i> B ,且 A 与 B 均为序列一致的原子操作</div>
|
|
<div class="t-li1"><span class="t-li">3)</span> A <i>先序于</i> X , X <i>简单先发生于</i> Y ,而 Y <i>先序于</i> B</div>
|
|
<div class="t-li1"><span class="t-li">4)</span> A <i>强先发生于</i> X ,而 X <i>强先发生于</i> B</div>
|
|
<p>注:非正式而言,若 A <i>强先发生于</i> B ,则在所有环境中 A 均显得在 B 之前得到求值。</p>
|
|
<p>注:<i>强先发生于</i>排除消费操作。</p>
|
|
</td>
|
|
<td><span class="t-mark-rev t-since-cxx20">(C++20 起)</span></td>
|
|
</tr>
|
|
</table>
|
|
<h4><span class="mw-headline" id=".E5.8F.AF.E8.A7.81.E5.89.AF.E6.95.88.E5.BA.94">可见副效应</span></h4>
|
|
<p>若下列皆为真,则标量 M 上的副效应 A (写入)相对于 M 上的值计算(读取)<i>可见</i>:</p>
|
|
<div class="t-li1"><span class="t-li">1)</span> A <i>先发生于</i> B</div>
|
|
<div class="t-li1"><span class="t-li">2)</span> 没有其他对 M 的副效应 X 满足 A <i>先发生于</i> X 且 X <i>先发生于</i> B</div>
|
|
<p>若副效应 A 相对于值计算 B 可见,则<i>修改顺序</i>中,满足 B 不<i>先发生于</i>它的对 M 的副效应的最长相接子集,被称为<i>副效应的可见序列</i>。( B 所确定的 M 的值,将是这些副效应之一所存储的值)</p>
|
|
<p>注意:线程间同步可归结为避免数据竞争(通过建立先发生于关系),及定义在何种条件下哪些副效应成为可见。</p>
|
|
<h4><span class="mw-headline" id=".E6.B6.88.E8.B4.B9.E6.93.8D.E4.BD.9C">消费操作</span></h4>
|
|
<p>带 <code>memory_order_consume</code> 或更强标签的原子加载是消费操作。注意 <span class="t-lc"><a href="cpp-atomic-atomic_thread_fence.html">std::atomic_thread_fence</a></span> 会施加比消费操作更强的同步要求。</p>
|
|
<h4><span class="mw-headline" id=".E8.8E.B7.E5.BE.97.E6.93.8D.E4.BD.9C">获得操作</span></h4>
|
|
<p>带 <code>memory_order_acquire</code> 或更强标签的原子加载是获得操作。<a href="cpp-named_req-Mutex.html"><span style="font-style:italic">互斥体</span> <span style="font-family: Georgia, 'DejaVu Serif', serif; font-style:italic">(Mutex)</span></a> 上的 lock() 操作亦为获得操作。注意 <span class="t-lc"><a href="cpp-atomic-atomic_thread_fence.html">std::atomic_thread_fence</a></span> 会施加比获得操作更强的同步要求。</p>
|
|
<h4><span class="mw-headline" id=".E9.87.8A.E6.94.BE.E6.93.8D.E4.BD.9C">释放操作</span></h4>
|
|
<p>带 <code>memory_order_release</code> 或更强标签的原子存储是释放操作。<a href="cpp-named_req-Mutex.html"><span style="font-style:italic">互斥体</span> <span style="font-family: Georgia, 'DejaVu Serif', serif; font-style:italic">(Mutex)</span></a> 上的 unlock() 操作亦为释放操作。注意 <span class="t-lc"><a href="cpp-atomic-atomic_thread_fence.html">std::atomic_thread_fence</a></span> 会施加比释放操作更强的同步要求。</p>
|
|
<h3><span class="mw-headline" id=".E8.A7.A3.E9.87.8A">解释</span></h3>
|
|
<h4><span class="mw-headline" id=".E5.AE.BD.E6.9D.BE.E9.A1.BA.E5.BA.8F">宽松顺序</span></h4>
|
|
<p>带标签 <span class="t-c"><span class="mw-geshi cpp source-cpp">memory_order_relaxed</span></span> 的原子操作无同步操作;它们不会在共时的内存访问间强加顺序。它们只保证原子性和修改顺序一致性。</p>
|
|
<p>例如,对于最初为零的 <code>x</code> 和 <code>y</code> ,</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="co1">// 线程 1 :</span>
|
|
r1 <span class="sy1">=</span> y.<span class="me1">load</span><span class="br0">(</span>std<span class="sy4">::</span><span class="me2">memory_order_relaxed</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// A</span>
|
|
x.<span class="me1">store</span><span class="br0">(</span>r1, std<span class="sy4">::</span><span class="me2">memory_order_relaxed</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// B</span>
|
|
<span class="co1">// 线程 2 :</span>
|
|
r2 <span class="sy1">=</span> x.<span class="me1">load</span><span class="br0">(</span>std<span class="sy4">::</span><span class="me2">memory_order_relaxed</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// C </span>
|
|
y.<span class="me1">store</span><span class="br0">(</span><span class="nu0">42</span>, std<span class="sy4">::</span><span class="me2">memory_order_relaxed</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// D</span></pre></div>
|
|
</div>
|
|
<p>允许产生结果 <code>r1 == 42 && r2 == 42</code> ,因为即使线程 1 中 A <i>先序于</i> B 且线程 2 中 C <i>先序于</i> D ,却没有制约避免 <code>y</code> 的修改顺序中 D 先出现于 A ,而 <code>x</code> 的修改顺序中 B 先出现于 C 。 D 在 <code>y</code> 上的副效应,可能可见于线程 1 中的加载 A ,同时 B 在 <code>x</code> 上的副效应,可能可见于线程 2 中的加载 C 。</p>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-since-cxx14">
|
|
<td>
|
|
<p>即使使用宽松内存模型,也不允许“无中生有”的值循环地依赖于其各自的计算,例如,对于最初为零的 <code>x</code> 和 <code>y</code> ,</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="co1">// 线程1:</span>
|
|
r1 <span class="sy1">=</span> x.<span class="me1">load</span><span class="br0">(</span>std<span class="sy4">::</span><span class="me2">memory_order_relaxed</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="kw1">if</span> <span class="br0">(</span>r1 <span class="sy1">==</span> <span class="nu0">42</span><span class="br0">)</span> y.<span class="me1">store</span><span class="br0">(</span>r1, std<span class="sy4">::</span><span class="me2">memory_order_relaxed</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="co1">// 线程2:</span>
|
|
r2 <span class="sy1">=</span> y.<span class="me1">load</span><span class="br0">(</span>memory_order_relaxed<span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="kw1">if</span> <span class="br0">(</span>r2 <span class="sy1">==</span> <span class="nu0">42</span><span class="br0">)</span> x.<span class="me1">store</span><span class="br0">(</span><span class="nu0">42</span>, std<span class="sy4">::</span><span class="me2">memory_order_relaxed</span><span class="br0">)</span><span class="sy4">;</span></pre></div>
|
|
</div>
|
|
<p>不允许产生结果 <code>r1 == 42 && r2 == 42</code> ,因为存储 42 于 <code>y</code> 只在存储 42 于 <code>x</code> 后有可能,这又循环依赖于存储 42 于 <code>y</code> 。注意 C++14 前规范曾在技术上允许,但不推荐实现者如此实现。</p>
|
|
</td>
|
|
<td><span class="t-mark-rev t-since-cxx14">(C++14 起)</span></td>
|
|
</tr>
|
|
</table>
|
|
<p>宽松内存顺序的典型使用是计数器自增,例如 <span class="t-lc"><a href="cpp-memory-shared_ptr.html">std::shared_ptr</a></span> 的引用计数器,因为这只要求原子性,但不要求顺序或同步(注意 <span class="t-lc"><a href="cpp-memory-shared_ptr.html">std::shared_ptr</a></span> 计数器自减要求与析构函数进行获得释放同步)</p>
|
|
<div class="t-example">
|
|
<div class="t-example-live-link"></div>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="co2">#include <vector></span>
|
|
<span class="co2">#include <iostream></span>
|
|
<span class="co2">#include <thread></span>
|
|
<span class="co2">#include <atomic></span>
|
|
|
|
<a href="cpp-atomic-atomic.html"><span class="kw2066">std::<span class="me2">atomic</span></span></a><span class="sy1"><</span><span class="kw4">int</span><span class="sy1">></span> cnt <span class="sy1">=</span> <span class="br0">{</span><span class="nu0">0</span><span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<span class="kw1">for</span> <span class="br0">(</span><span class="kw4">int</span> n <span class="sy1">=</span> <span class="nu0">0</span><span class="sy4">;</span> n <span class="sy1"><</span> <span class="nu0">1000</span><span class="sy4">;</span> <span class="sy2">++</span>n<span class="br0">)</span> <span class="br0">{</span>
|
|
cnt.<span class="me1">fetch_add</span><span class="br0">(</span><span class="nu0">1</span>, std<span class="sy4">::</span><span class="me2">memory_order_relaxed</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">int</span> main<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<a href="cpp-container-vector.html"><span class="kw1269">std::<span class="me2">vector</span></span></a><span class="sy1"><</span><a href="cpp-thread-thread.html"><span class="kw2139">std::<span class="me2">thread</span></span></a><span class="sy1">></span> v<span class="sy4">;</span>
|
|
<span class="kw1">for</span> <span class="br0">(</span><span class="kw4">int</span> n <span class="sy1">=</span> <span class="nu0">0</span><span class="sy4">;</span> n <span class="sy1"><</span> <span class="nu0">10</span><span class="sy4">;</span> <span class="sy2">++</span>n<span class="br0">)</span> <span class="br0">{</span>
|
|
v.<span class="me1">emplace_back</span><span class="br0">(</span>f<span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
<span class="kw1">for</span> <span class="br0">(</span><span class="kw4">auto</span><span class="sy3">&</span> t <span class="sy4">:</span> v<span class="br0">)</span> <span class="br0">{</span>
|
|
t.<span class="me1">join</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
<a href="cpp-io-cout.html"><span class="kw1758">std::<span class="me2">cout</span></span></a> <span class="sy1"><<</span> <span class="st0">"Final counter value is "</span> <span class="sy1"><<</span> cnt <span class="sy1"><<</span> <span class="st0">'<span class="es1">\n</span>'</span><span class="sy4">;</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<p>输出:</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="text source-text">
|
|
<pre class="de1">Final counter value is 10000</pre></div>
|
|
</div>
|
|
</div>
|
|
<h4><span class="mw-headline" id=".E9.87.8A.E6.94.BE.E8.8E.B7.E5.BE.97.E9.A1.BA.E5.BA.8F">释放获得顺序</span></h4>
|
|
<p>若线程 A 中的一个原子存储带标签 <span class="t-c"><span class="mw-geshi cpp source-cpp">memory_order_release</span></span> ,而线程 B 中来自同一变量的原子加载带标签 <span class="t-c"><span class="mw-geshi cpp source-cpp">memory_order_acquire</span></span> ,则从线程 A 的视角<i>先发生于</i>原子存储的所有内存写入(非原子及宽松原子的),在线程 B 中成为<i>可见副效应</i>,即一旦原子加载完成,则保证线程 B 能观察到线程 A 写入内存的所有内容。</p>
|
|
<p>同步仅建立在<i>释放</i>和<i>获得</i>同一原子对象的线程之间。其他线程可能看到与被同步线程的一者或两者相异的内存访问顺序。</p>
|
|
<p>在强顺序系统( x86 、 SPARC TSO 、 IBM 主框架)上,释放获得顺序对于多数操作是自动进行的。无需为此同步模式添加额外的 CPU 指令,只有某些编译器优化受影响(例如,编译器被禁止将非原子存储移到原子存储-释放后,或将非原子加载移到原子加载-获得前)。在弱顺序系统( ARM 、 Itanium 、 Power PC )上,必须使用特别的 CPU 加载或内存栅栏指令。</p>
|
|
<p>互斥锁(例如 <span class="t-lc"><a href="cpp-thread-mutex.html">std::mutex</a></span> 或<a href="cpp-atomic-atomic_flag.html">原子自旋锁</a>)是释放获得同步的例子:线程 A 释放锁而线程 B 获得它时,发生于线程 A 环境的临界区(释放之前)中的所有事件,必须对于执行同一临界区的线程 B (获得之后)可见。</p>
|
|
<div class="t-example">
|
|
<div class="t-example-live-link"></div>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="co2">#include <thread></span>
|
|
<span class="co2">#include <atomic></span>
|
|
<span class="co2">#include <cassert></span>
|
|
<span class="co2">#include <string></span>
|
|
|
|
<a href="cpp-atomic-atomic.html"><span class="kw2066">std::<span class="me2">atomic</span></span></a><span class="sy1"><</span><a href="cpp-string-basic_string.html"><span class="kw1230">std::<span class="me2">string</span></span></a><span class="sy2">*</span><span class="sy1">></span> ptr<span class="sy4">;</span>
|
|
<span class="kw4">int</span> data<span class="sy4">;</span>
|
|
|
|
<span class="kw4">void</span> producer<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<a href="cpp-string-basic_string.html"><span class="kw1230">std::<span class="me2">string</span></span></a><span class="sy2">*</span> p <span class="sy1">=</span> new <a href="cpp-string-basic_string.html"><span class="kw1230">std::<span class="me2">string</span></span></a><span class="br0">(</span><span class="st0">"Hello"</span><span class="br0">)</span><span class="sy4">;</span>
|
|
data <span class="sy1">=</span> <span class="nu0">42</span><span class="sy4">;</span>
|
|
ptr.<span class="me1">store</span><span class="br0">(</span>p, std<span class="sy4">::</span><span class="me2">memory_order_release</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">void</span> consumer<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<a href="cpp-string-basic_string.html"><span class="kw1230">std::<span class="me2">string</span></span></a><span class="sy2">*</span> p2<span class="sy4">;</span>
|
|
<span class="kw1">while</span> <span class="br0">(</span><span class="sy3">!</span><span class="br0">(</span>p2 <span class="sy1">=</span> ptr.<span class="me1">load</span><span class="br0">(</span>std<span class="sy4">::</span><span class="me2">memory_order_acquire</span><span class="br0">)</span><span class="br0">)</span><span class="br0">)</span>
|
|
<span class="sy4">;</span>
|
|
<a href="cpp-error-assert.html"><span class="kw774">assert</span></a><span class="br0">(</span><span class="sy2">*</span>p2 <span class="sy1">==</span> <span class="st0">"Hello"</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 绝无问题</span>
|
|
<a href="cpp-error-assert.html"><span class="kw774">assert</span></a><span class="br0">(</span>data <span class="sy1">==</span> <span class="nu0">42</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 绝无问题</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">int</span> main<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<a href="cpp-thread-thread.html"><span class="kw2139">std::<span class="me2">thread</span></span></a> t1<span class="br0">(</span>producer<span class="br0">)</span><span class="sy4">;</span>
|
|
<a href="cpp-thread-thread.html"><span class="kw2139">std::<span class="me2">thread</span></span></a> t2<span class="br0">(</span>consumer<span class="br0">)</span><span class="sy4">;</span>
|
|
t1.<span class="me1">join</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> t2.<span class="me1">join</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
</div>
|
|
<div class="t-example">
|
|
<p>下例演示三个线程间传递性的释放获得顺序</p>
|
|
<div class="t-example-live-link"></div>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="co2">#include <thread></span>
|
|
<span class="co2">#include <atomic></span>
|
|
<span class="co2">#include <cassert></span>
|
|
<span class="co2">#include <vector></span>
|
|
|
|
<a href="cpp-container-vector.html"><span class="kw1269">std::<span class="me2">vector</span></span></a><span class="sy1"><</span><span class="kw4">int</span><span class="sy1">></span> data<span class="sy4">;</span>
|
|
<a href="cpp-atomic-atomic.html"><span class="kw2066">std::<span class="me2">atomic</span></span></a><span class="sy1"><</span><span class="kw4">int</span><span class="sy1">></span> flag <span class="sy1">=</span> <span class="br0">{</span><span class="nu0">0</span><span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="kw4">void</span> thread_1<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
data.<span class="me1">push_back</span><span class="br0">(</span><span class="nu0">42</span><span class="br0">)</span><span class="sy4">;</span>
|
|
flag.<span class="me1">store</span><span class="br0">(</span><span class="nu0">1</span>, std<span class="sy4">::</span><span class="me2">memory_order_release</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">void</span> thread_2<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<span class="kw4">int</span> expected<span class="sy1">=</span><span class="nu0">1</span><span class="sy4">;</span>
|
|
<span class="kw1">while</span> <span class="br0">(</span><span class="sy3">!</span>flag.<span class="me1">compare_exchange_strong</span><span class="br0">(</span>expected, <span class="nu0">2</span>, std<span class="sy4">::</span><span class="me2">memory_order_acq_rel</span><span class="br0">)</span><span class="br0">)</span> <span class="br0">{</span>
|
|
expected <span class="sy1">=</span> <span class="nu0">1</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">void</span> thread_3<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<span class="kw1">while</span> <span class="br0">(</span>flag.<span class="me1">load</span><span class="br0">(</span>std<span class="sy4">::</span><span class="me2">memory_order_acquire</span><span class="br0">)</span> <span class="sy1"><</span> <span class="nu0">2</span><span class="br0">)</span>
|
|
<span class="sy4">;</span>
|
|
<a href="cpp-error-assert.html"><span class="kw774">assert</span></a><span class="br0">(</span>data.<span class="me1">at</span><span class="br0">(</span><span class="nu0">0</span><span class="br0">)</span> <span class="sy1">==</span> <span class="nu0">42</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 决不出错</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">int</span> main<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<a href="cpp-thread-thread.html"><span class="kw2139">std::<span class="me2">thread</span></span></a> a<span class="br0">(</span>thread_1<span class="br0">)</span><span class="sy4">;</span>
|
|
<a href="cpp-thread-thread.html"><span class="kw2139">std::<span class="me2">thread</span></span></a> b<span class="br0">(</span>thread_2<span class="br0">)</span><span class="sy4">;</span>
|
|
<a href="cpp-thread-thread.html"><span class="kw2139">std::<span class="me2">thread</span></span></a> c<span class="br0">(</span>thread_3<span class="br0">)</span><span class="sy4">;</span>
|
|
a.<span class="me1">join</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> b.<span class="me1">join</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> c.<span class="me1">join</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
</div>
|
|
<h4><span class="mw-headline" id=".E9.87.8A.E6.94.BE.E6.B6.88.E8.B4.B9.E9.A1.BA.E5.BA.8F">释放消费顺序</span></h4>
|
|
<p>若线程 A 中的原子存储带标签 <span class="t-c"><span class="mw-geshi cpp source-cpp">memory_order_release</span></span> 而线程 B 中来自同一对象的读取存储值的原子加载带标签 <span class="t-c"><span class="mw-geshi cpp source-cpp">memory_order_consume</span></span> ,则线程 A 视角中<i>先发生于</i>原子存储的所有内存写入(非原子和宽松原子的),会在线程 B 中该加载操作所<i>携带依赖</i>进入的操作中变成<i>可见副效应</i>,即一旦完成原子加载,则保证线程B中,使用从该加载获得的值的运算符和函数,能见到线程 A 写入内存的内容。</p>
|
|
<p>同步仅在<i>释放</i>和<i>消费</i>同一原子对象的线程间建立。其他线程能见到与被同步线程的一者或两者相异的内存访问顺序。</p>
|
|
<p>所有异于 DEC Alpha 的主流 CPU 上,依赖顺序是自动的,无需为此同步模式产生附加的 CPU 指令,只有某些编译器优化收益受影响(例如,编译器被禁止牵涉到依赖链的对象上的推测性加载)。</p>
|
|
<p>此顺序的典型使用情况,涉及对很少被写入的数据结构(安排表、配置、安全策略、防火墙规则等)的共时读取,和有指针中介发布的发布者-订阅者情形,即当生产者发布消费者能通过其访问信息的指针之时:无需令生产者写入内存的所有其他内容对消费者可见(这在弱顺序架构上可能是昂贵的操作)。这种场景的例子之一是 <a href="https://en.wikipedia.org/wiki/Read-copy-update" class="extiw" title="enwiki:Read-copy-update">rcu 解引用</a>。</p>
|
|
<p>细粒度依赖链控制可参阅 <span class="t-lc"><a href="cpp-atomic-kill_dependency.html">std::kill_dependency</a></span> 及 <code>[[<a href="cpp-language-attributes-carries_dependency.html">carries_dependency</a>]]</code> 。</p>
|
|
<p>注意到 2015 年 2 月为止没有产品编译器跟踪依赖链:均将消费操作提升为获得操作。</p>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-since-cxx17">
|
|
<td>
|
|
<p>释放消费顺序的规范正在修订中,而且暂时不鼓励使用 <code>memory_order_consume</code> 。</p>
|
|
</td>
|
|
<td><span class="t-mark-rev t-since-cxx17">(C++17 起)</span></td>
|
|
</tr>
|
|
</table>
|
|
<div class="t-example">
|
|
<p>此示例演示用于指针中介的发布的依赖定序同步:int data不由数据依赖关系关联到指向字符串的指针,从而其值在消费者中未定义。</p>
|
|
<div class="t-example-live-link"></div>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="co2">#include <thread></span>
|
|
<span class="co2">#include <atomic></span>
|
|
<span class="co2">#include <cassert></span>
|
|
<span class="co2">#include <string></span>
|
|
|
|
<a href="cpp-atomic-atomic.html"><span class="kw2066">std::<span class="me2">atomic</span></span></a><span class="sy1"><</span><a href="cpp-string-basic_string.html"><span class="kw1230">std::<span class="me2">string</span></span></a><span class="sy2">*</span><span class="sy1">></span> ptr<span class="sy4">;</span>
|
|
<span class="kw4">int</span> data<span class="sy4">;</span>
|
|
|
|
<span class="kw4">void</span> producer<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<a href="cpp-string-basic_string.html"><span class="kw1230">std::<span class="me2">string</span></span></a><span class="sy2">*</span> p <span class="sy1">=</span> new <a href="cpp-string-basic_string.html"><span class="kw1230">std::<span class="me2">string</span></span></a><span class="br0">(</span><span class="st0">"Hello"</span><span class="br0">)</span><span class="sy4">;</span>
|
|
data <span class="sy1">=</span> <span class="nu0">42</span><span class="sy4">;</span>
|
|
ptr.<span class="me1">store</span><span class="br0">(</span>p, std<span class="sy4">::</span><span class="me2">memory_order_release</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">void</span> consumer<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<a href="cpp-string-basic_string.html"><span class="kw1230">std::<span class="me2">string</span></span></a><span class="sy2">*</span> p2<span class="sy4">;</span>
|
|
<span class="kw1">while</span> <span class="br0">(</span><span class="sy3">!</span><span class="br0">(</span>p2 <span class="sy1">=</span> ptr.<span class="me1">load</span><span class="br0">(</span>std<span class="sy4">::</span><span class="me2">memory_order_consume</span><span class="br0">)</span><span class="br0">)</span><span class="br0">)</span>
|
|
<span class="sy4">;</span>
|
|
<a href="cpp-error-assert.html"><span class="kw774">assert</span></a><span class="br0">(</span><span class="sy2">*</span>p2 <span class="sy1">==</span> <span class="st0">"Hello"</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 绝无出错: *p2 从 ptr 携带依赖</span>
|
|
<a href="cpp-error-assert.html"><span class="kw774">assert</span></a><span class="br0">(</span>data <span class="sy1">==</span> <span class="nu0">42</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 可能也可能不会出错: data 不从 ptr 携带依赖</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">int</span> main<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<a href="cpp-thread-thread.html"><span class="kw2139">std::<span class="me2">thread</span></span></a> t1<span class="br0">(</span>producer<span class="br0">)</span><span class="sy4">;</span>
|
|
<a href="cpp-thread-thread.html"><span class="kw2139">std::<span class="me2">thread</span></span></a> t2<span class="br0">(</span>consumer<span class="br0">)</span><span class="sy4">;</span>
|
|
t1.<span class="me1">join</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> t2.<span class="me1">join</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
</div>
|
|
<p><br /></p>
|
|
<h4><span class="mw-headline" id=".E5.BA.8F.E5.88.97.E4.B8.80.E8.87.B4.E9.A1.BA.E5.BA.8F">序列一致顺序</span></h4>
|
|
<p>带标签 <span class="t-c"><span class="mw-geshi cpp source-cpp">memory_order_seq_cst</span></span> 的原子操作不仅以与释放/获得顺序相同的方式排序内存(在一个线程中<i>先发生于</i>存储的任何结果都变成进行加载的线程中的<i>可见副效应</i>),还对所有带此标签的内存操作建立<i>单独全序</i>。</p>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-until-cxx20">
|
|
<td>
|
|
<p>正式而言,</p>
|
|
<p>每个加载原子对象 M 的 <code>memory_order_seq_cst</code> 操作 B ,观测到以下之一:</p>
|
|
<ul>
|
|
<li>修改 M 的上个操作 A 的结果,A 在单独全序中先出现于 B</li>
|
|
<li>或若存在这种 A ,则 B 可能观测到某些 M 的修改结果,这些修改非 <code>memory_order_seq_cst</code> 而且不<i>先发生于</i> A</li>
|
|
<li>或若不存在这种 A ,则 B 可能观测到某些 M 的无关联修改,这些修改非 <code>memory_order_seq_cst</code></li>
|
|
</ul>
|
|
<p>若存在 <code>memory_order_seq_cst</code>的 <span class="t-lc"><a href="cpp-atomic-atomic_thread_fence.html">std::atomic_thread_fence</a></span> 操作 X <i>先序于</i> B ,则 B 观测到以下之一:</p>
|
|
<ul>
|
|
<li>在单独全序中先出现于 X 的上个 M 的 <code>memory_order_seq_cst</code> 修改</li>
|
|
<li>在单独全序中后出现于它的某些 M 的无关联修改</li>
|
|
</ul>
|
|
<p>设有一对 M 上的原子操作,称之为 A 和 B ,这里 A 写入、 B 读取 M 的值,若存在二个 <code>memory_order_seq_cst</code> 的 <span class="t-lc"><a href="cpp-atomic-atomic_thread_fence.html">std::atomic_thread_fence</a></span> X 和 Y ,且若 A <i>先序于</i> X , Y <i>先序于</i> B ,且 X 在单独全序中先出现于 Y ,则 B 观测到二者之一:</p>
|
|
<ul>
|
|
<li>A 的效应</li>
|
|
<li>某些在 M 的修改顺序中后出现于 A 的无关联修改</li>
|
|
</ul>
|
|
<p>设有一对 M 上的原子操作,称之为 A 和 B ,若符合下列条件之一,则 M 的修改顺序中 B 先发生于 A</p>
|
|
<ul>
|
|
<li>存在一个 <code>memory_order_seq_cst</code> 的 <span class="t-lc"><a href="cpp-atomic-atomic_thread_fence.html">std::atomic_thread_fence</a></span> X ,它满足 A <i>先序于</i> X ,且 X 在单独全序中先出现于 B</li>
|
|
<li>或者,存在一个 <code>memory_order_seq_cst</code> 的 <span class="t-lc"><a href="cpp-atomic-atomic_thread_fence.html">std::atomic_thread_fence</a></span> Y ,它满足 Y <i>先序于</i> B ,且 A 在单独全序中先出现于 Y</li>
|
|
<li>或者,存在 <code>memory_order_seq_cst</code> 的 <span class="t-lc"><a href="cpp-atomic-atomic_thread_fence.html">std::atomic_thread_fence</a></span> X 和 Y ,它们满足 A <i>先序于</i> X , Y <i>先序于</i> B ,且 X 在单独全序中先出现于 Y</li>
|
|
</ul>
|
|
<p>注意这表明:</p>
|
|
<div class="t-li1"><span class="t-li">1)</span> 只要不带 <code>memory_order_seq_cst</code> 标签的原子操作进入局面,则立即丧失序列一致性</div>
|
|
<div class="t-li1"><span class="t-li">2)</span> 序列一致栅栏仅为栅栏自身建立全序,而不为通常情况下的原子操作建立(<i>先序于</i>不是跨线程关系,不同于<i>先发生于</i>)</div>
|
|
</td>
|
|
<td><span class="t-mark-rev t-until-cxx20">(C++20 前)</span></td>
|
|
</tr>
|
|
<tr class="t-rev t-since-cxx20">
|
|
<td>
|
|
<p>正式而言,</p>
|
|
<p>某原子对象 M 上的原子操作<i>连贯先序于</i> M 上的另一原子操作 B ,若下列任一为真:</p>
|
|
<div class="t-li1"><span class="t-li">1)</span> A 是修改,而 B 读取 A 所存储的值</div>
|
|
<div class="t-li1"><span class="t-li">2)</span> A 在 M 的<i>修改顺序</i>中前于 B</div>
|
|
<div class="t-li1"><span class="t-li">3)</span> A 读取原子操作 X 所存储的值,而 X 在<i>修改顺序</i>中前于 B ,且 A 与 B 不是同一读修改写操作</div>
|
|
<div class="t-li1"><span class="t-li">4)</span> A <i>连贯先序于</i> X ,而 X <i>连贯先序于</i> B</div>
|
|
<p>所有 <code>memory_order_seq_cst</code> 操作,包括栅栏上,有单独全序 S ,它满足下列制约:</p>
|
|
<div class="t-li1"><span class="t-li">1)</span> 若 A 与 B 为 <code>memory_order_seq_cst</code> 操作,而 A <i>强先发生于</i> B ,则 A 在 S 中前于 B</div>
|
|
<div class="t-li1"><span class="t-li">2)</span> 对于对象 M 上的每对原子操作 A 与 B ,其中 A <i>连贯先序于</i> B :</div>
|
|
<div class="t-li2"><span class="t-li">a)</span> 若 A 与 B 都是 <code>memory_order_seq_cst</code> 操作,则 S 中 A 前于 B</div>
|
|
<div class="t-li2"><span class="t-li">b)</span> 若 A 是 <code>memory_order_seq_cst</code> 操作,而 B <i>先发生于</i> <code>memory_order_seq_cst</code> 栅栏 Y ,则 S 中 A 前于 Y</div>
|
|
<div class="t-li2"><span class="t-li">c)</span> 若 <code>memory_order_seq_cst</code> 栅栏 X <i>先发生于</i> A ,而 B 为 <code>memory_order_seq_cst</code> 操作,则 S 中 X 前于 B</div>
|
|
<div class="t-li2"><span class="t-li">d)</span> 若 <code>memory_order_seq_cst</code> 栅栏 X <i>先发生于</i> A ,而 B <i>先发生于</i> <code>memory_order_seq_cst</code> 栅栏 Y ,则 S 中 X 前于 Y</div>
|
|
<p>正式定义确保:</p>
|
|
<div class="t-li1"><span class="t-li">1)</span> 单独全序与任何原子对象的<i>修改顺序</i>一致</div>
|
|
<div class="t-li1"><span class="t-li">2)</span> <code>memory_order_seq_cst</code> 加载从最后的 <code>memory_order_seq_cst</code> 修改,或从某个不<i>先发生于</i>顺序中之前的 <code>memory_order_seq_cst</code> 修改操作的非 <code>memory_order_seq_cst</code> 修改之一获取其值。</div>
|
|
<p>单独全序可能与<i>先发生于</i>不一致。这允许 <code>memory_order_acquire</code> 与 <code>memory_order_release</code> 在某些 CPU 上的更高效实现。当 <code>memory_order_acquire</code> 及 <code>memory_order_release</code> 与 <code>memory_order_seq_cst</code> 混合时,这能产生惊异的结果。</p>
|
|
<p>例如,对于初值为零的 <code>x</code> 与 <code>y</code> ,</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="co1">// 线程 1 :</span>
|
|
x.<span class="me1">store</span><span class="br0">(</span><span class="nu0">1</span>, std<span class="sy4">::</span><span class="me2">memory_order_seq_cst</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// A</span>
|
|
y.<span class="me1">store</span><span class="br0">(</span><span class="nu0">1</span>, std<span class="sy4">::</span><span class="me2">memory_order_release</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// B</span>
|
|
<span class="co1">// 线程 2 :</span>
|
|
r1 <span class="sy1">=</span> y.<span class="me1">fetch_add</span><span class="br0">(</span><span class="nu0">1</span>, std<span class="sy4">::</span><span class="me2">memory_order_seq_cst</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// C</span>
|
|
r2 <span class="sy1">=</span> y.<span class="me1">load</span><span class="br0">(</span>std<span class="sy4">::</span><span class="me2">memory_order_relaxed</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// D</span>
|
|
<span class="co1">// 线程 3 :</span>
|
|
y.<span class="me1">store</span><span class="br0">(</span><span class="nu0">3</span>, std<span class="sy4">::</span><span class="me2">memory_order_seq_cst</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// E</span>
|
|
r3 <span class="sy1">=</span> x.<span class="me1">load</span><span class="br0">(</span>std<span class="sy4">::</span><span class="me2">memory_order_seq_cst</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// F</span></pre></div>
|
|
</div>
|
|
<p>允许这些操作产生 <code>r1 == 1 && r2 == 3 && r3 == 0</code> 的结果,其中 A <i>先发生于</i> C ,但 <code>memory_order_seq_cst</code> 的单独全序 C-E-F-A 中 C 前于 A (见 <a rel="nofollow" class="external text" href="https://plv.mpi-sws.org/scfix/paper.pdf">Lahav 等</a>)。</p>
|
|
<p>注意:</p>
|
|
<div class="t-li1"><span class="t-li">1)</span> 一旦不带 <code>memory_order_seq_cst</code> 标签的原子操作进入局面,程序的序列一致保证就会立即丧失</div>
|
|
<div class="t-li1"><span class="t-li">2)</span> 多数情况下, <code>memory_order_seq_cst</code> 原子操作相对于同一线程所进行的其他原子操作可重排</div>
|
|
</td>
|
|
<td><span class="t-mark-rev t-since-cxx20">(C++20 起)</span></td>
|
|
</tr>
|
|
</table>
|
|
<p>若在多生产者-多消费者的情形中,且所有消费者都必须以相同顺序观察到所有生产者的动作出现,则可能必须有序列顺序。</p>
|
|
<p>全序列顺序在所有多核系统上要求完全的内存栅栏 CPU 指令。这可能成为性能瓶颈,因为它强制受影响的内存访问传播到每个核心。</p>
|
|
<div class="t-example">
|
|
<p>此示例演示序列一直顺序为必要的场合。任何其他顺序都可能触发assert,因为可能令线程<code>c</code>和<code>d</code>观测到原子对象<code>x</code>和<code>y</code>以相反顺序更改。</p>
|
|
<div class="t-example-live-link"></div>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="co2">#include <thread></span>
|
|
<span class="co2">#include <atomic></span>
|
|
<span class="co2">#include <cassert></span>
|
|
|
|
<a href="cpp-atomic-atomic.html"><span class="kw2066">std::<span class="me2">atomic</span></span></a><span class="sy1"><</span><span class="kw4">bool</span><span class="sy1">></span> x <span class="sy1">=</span> <span class="br0">{</span><span class="kw2">false</span><span class="br0">}</span><span class="sy4">;</span>
|
|
<a href="cpp-atomic-atomic.html"><span class="kw2066">std::<span class="me2">atomic</span></span></a><span class="sy1"><</span><span class="kw4">bool</span><span class="sy1">></span> y <span class="sy1">=</span> <span class="br0">{</span><span class="kw2">false</span><span class="br0">}</span><span class="sy4">;</span>
|
|
<a href="cpp-atomic-atomic.html"><span class="kw2066">std::<span class="me2">atomic</span></span></a><span class="sy1"><</span><span class="kw4">int</span><span class="sy1">></span> z <span class="sy1">=</span> <span class="br0">{</span><span class="nu0">0</span><span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="kw4">void</span> write_x<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
x.<span class="me1">store</span><span class="br0">(</span><span class="kw2">true</span>, std<span class="sy4">::</span><span class="me2">memory_order_seq_cst</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">void</span> write_y<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
y.<span class="me1">store</span><span class="br0">(</span><span class="kw2">true</span>, std<span class="sy4">::</span><span class="me2">memory_order_seq_cst</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">void</span> read_x_then_y<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<span class="kw1">while</span> <span class="br0">(</span><span class="sy3">!</span>x.<span class="me1">load</span><span class="br0">(</span>std<span class="sy4">::</span><span class="me2">memory_order_seq_cst</span><span class="br0">)</span><span class="br0">)</span>
|
|
<span class="sy4">;</span>
|
|
<span class="kw1">if</span> <span class="br0">(</span>y.<span class="me1">load</span><span class="br0">(</span>std<span class="sy4">::</span><span class="me2">memory_order_seq_cst</span><span class="br0">)</span><span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="sy2">++</span>z<span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">void</span> read_y_then_x<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<span class="kw1">while</span> <span class="br0">(</span><span class="sy3">!</span>y.<span class="me1">load</span><span class="br0">(</span>std<span class="sy4">::</span><span class="me2">memory_order_seq_cst</span><span class="br0">)</span><span class="br0">)</span>
|
|
<span class="sy4">;</span>
|
|
<span class="kw1">if</span> <span class="br0">(</span>x.<span class="me1">load</span><span class="br0">(</span>std<span class="sy4">::</span><span class="me2">memory_order_seq_cst</span><span class="br0">)</span><span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="sy2">++</span>z<span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">int</span> main<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<a href="cpp-thread-thread.html"><span class="kw2139">std::<span class="me2">thread</span></span></a> a<span class="br0">(</span>write_x<span class="br0">)</span><span class="sy4">;</span>
|
|
<a href="cpp-thread-thread.html"><span class="kw2139">std::<span class="me2">thread</span></span></a> b<span class="br0">(</span>write_y<span class="br0">)</span><span class="sy4">;</span>
|
|
<a href="cpp-thread-thread.html"><span class="kw2139">std::<span class="me2">thread</span></span></a> c<span class="br0">(</span>read_x_then_y<span class="br0">)</span><span class="sy4">;</span>
|
|
<a href="cpp-thread-thread.html"><span class="kw2139">std::<span class="me2">thread</span></span></a> d<span class="br0">(</span>read_y_then_x<span class="br0">)</span><span class="sy4">;</span>
|
|
a.<span class="me1">join</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> b.<span class="me1">join</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> c.<span class="me1">join</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> d.<span class="me1">join</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<a href="cpp-error-assert.html"><span class="kw774">assert</span></a><span class="br0">(</span>z.<span class="me1">load</span><span class="br0">(</span><span class="br0">)</span> <span class="sy3">!</span><span class="sy1">=</span> <span class="nu0">0</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 决不发生</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
</div>
|
|
<h3><span class="mw-headline" id=".E4.B8.8E_volatile_.E7.9A.84.E5.85.B3.E7.B3.BB">与 <span class="t-c"><span class="mw-geshi cpp source-cpp"><span class="kw4">volatile</span></span></span> 的关系</span></h3>
|
|
<p>在执行线程中,不能将通过 <a href="cpp-language-cv.html">volatile 泛左值</a>的访问(读和写)重排到同线程内<i>先序于</i>或<i>后序于</i>它的可观测副效应(包含其他 volatile 访问)后,但不保证另一线程观察到此顺序,因为 volatile 访问不建立线程间同步。</p>
|
|
<p>另外, volatile 访问不是原子的(共时的读和写是<a href="cpp-language-memory_model.html">数据竞争</a>),且不排序内存(非 volatile 内存访问可以自由地重排到 volatile 访问前后)。</p>
|
|
<p>一个值得注意的例外是 Visual Studio ,其中默认设置下,每个 volatile 写拥有释放语义,而每个 volatile 读拥有获得语义( <a rel="nofollow" class="external text" href="http://msdn.microsoft.com/en-us/library/12a04hfd(v=vs.100).aspx">MSDN</a> ),故而可将 volatile 对象用于线程间同步。标准的 <span class="t-c"><span class="mw-geshi cpp source-cpp"><span class="kw4">volatile</span></span></span> 语义不可应用于多线程编程,尽管它们在应用到 <span class="t-c"><span class="mw-geshi cpp source-cpp">sig_atomic_t</span></span> 对象时,足以与例如运行于同一线程的 <span class="t-lc"><a href="cpp-utility-program-signal.html">std::signal</a></span> 处理函数交流。</p>
|
|
<h3><span class="mw-headline" id=".E5.8F.82.E9.98.85">参阅</span></h3>
|
|
<table class="t-dsc-begin"></table>
|
|
<h3><span class="mw-headline" id=".E5.A4.96.E9.83.A8.E9.93.BE.E6.8E.A5">外部链接</span></h3>
|
|
<ul>
|
|
<li><a href="https://en.wikipedia.org/wiki/MOESI_protocol" class="extiw" title="enwiki:MOESI protocol">MOESI 协议</a></li>
|
|
</ul>
|
|
<table class="metadata plainlinks ambox mbox-small-left ambox-notice" style="">
|
|
<tr>
|
|
<td class="mbox-empty-cell"></td>
|
|
<td class="mbox-text" style="">本节未完成<br />
|
|
原因:我们在 QPI 、 MOESI 可能还有 Dragon 上找下好参考</td>
|
|
</tr>
|
|
</table>
|
|
<ul>
|
|
<li><a rel="nofollow" class="external text" href="http://www.cl.cam.ac.uk/~pes20/weakmemory/cacm.pdf">x86-TSO : x86 多处理器上严格而有用的程序员模型</a>,P. Sewell 等, 2010</li>
|
|
<li><a rel="nofollow" class="external text" href="http://www.cl.cam.ac.uk/~pes20/ppc-supplemental/test7.pdf">ARM 及 POWER 宽松内存模型的入门教程</a>, P. Sewell 等 , 2012</li>
|
|
<li><a rel="nofollow" class="external text" href="https://researchspace.auckland.ac.nz/bitstream/handle/2292/11594/MESIF-2009.pdf?sequence=6">MESIF :点对点互联的两跳缓存一致性协议</a>, J.R. Goodman, H.H.J. Hum , 2009</li>
|
|
</ul>
|
|
</div>
|
|
<div class="visualClear"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|