C++内存序

先后一致次序(memory_order_seq_cst)

如果程序服从先后一致次序,就简单地把一切事件视为按先后顺序发生,其操作与这种次序保持一致。假设在多线程程序的全部原子类型的实例上,所有的操作都保持先后一致,name它们将按某种特定次序改由单线程执行,则俩个程序的操作毫无区别。

缺点:在弱保序的多处理器计算机上,保序操作会带来很严重的性能损失。

例子:

复制代码
#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;

void write_x()
{
    x.store(true,std::memory_order_seq_cst);    ⇽---  ①
}

void write_y()
{
    y.store(true,std::memory_order_seq_cst);    ⇽---  ②
}

void read_x_then_y()
{
    while(!x.load(std::memory_order_seq_cst));
    if(y.load(std::memory_order_seq_cst))    ⇽---++z;
}

void read_y_then_x()
{
    while(!y.load(std::memory_order_seq_cst));
    if(x.load(std::memory_order_seq_cst))    ⇽---++z;
}

int main()
{
    x=false;
    y=false;
    z=0;
    std::thread a(write_x);
    std::thread b(write_y);
    std::thread c(read_x_then_y);
    std::thread d(read_y_then_x);
    a.join();
    b.join();
    c.join();
    d.join();
    assert(z.load()!=0);    ⇽---  ⑤
}
复制代码

 如何理解这个例子呢,关键点还是要理解原子变量。原子变量的意思就是,这个变量load进寄存器和store进内存,在代码里的顺序和真正执行的顺序要保持一致。所以我们先看x, x在write_x(先执行)和read_x_then_y(后执行),这两个函数里出现了。因为在代码的顺序是先write_x中出现,然后read_x_then_y, 所以所有代码的执行顺序是x.store(ture)   -》 (在write_x()中), x.load() 和 y.load() -》(在read_x_then_y)中,如果是y先执行。注意每个线程里的代码执行顺序都没有变。这个就是强序带来的。

 

获取-释放次序(memory_order_cosume, memory_order_acquire, memory_order_release和memory_order_acq_rel)

获取-释放次序比宽松次序严格一些,它会产生一定程度的同步效果,而不会形成先后一致次序的全局总操作序列,在该内存模型中,原子化载入即为获取操作(memory_order_acquire),原子化存储即为释放操作(memory_order_release),而原子化“读改写”操作(像fetch_add()和exchange())则为获取或释放操作,或两者都是(memory_order_acq_rel)。这种内存次序在成对的读写线程之间起到同步作用。释放和获取操作构成同步关系,前者写出的值由后者读取。换言之,若多个线程从获取释放-操作构成同步关系,则其缩减的操作序列可能差异,但差异的程度和方式都受到一定条件的制约。

例子:

宽松情况下

复制代码
#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x()
{
    x.store(true,std::memory_order_release);
}
void write_y()
{
    y.store(true,std::memory_order_release);
}
void read_x_then_y()
{
    while(!x.load(std::memory_order_acquire));
    if(y.load(std::memory_order_acquire))    ⇽---++z;
}
void read_y_then_x()
{
    while(!y.load(std::memory_order_acquire));
    if(x.load(std::memory_order_acquire))    ⇽---++z;
}
int main()
{
    x=false;
    y=false;
    z=0;
    std::thread a(write_x);
    std::thread b(write_y);
    std::thread c(read_x_then_y);
    std::thread d(read_y_then_x);
    a.join();
    b.join();
    c.join();
    d.join();
    assert(z.load()!=0);    ⇽---  ③
}
复制代码

 上述图中,什么情况都有可能发生。

获取-释放次序下,线程会有同步情况

例子:

复制代码
#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x_then_y()
{
    x.store(true,std::memory_order_relaxed);    ⇽---  ①
    y.store(true,std::memory_order_release);    ⇽---  ②
}
void read_y_then_x()
{
    while(!y.load(std::memory_order_acquire));    ⇽---  ③以自旋方式等待变量y的值设置为true
    if(x.load(std::memory_order_relaxed))    ⇽---++z;
}
int main()
{
    x=false;
    y=false;
    z=0;
    std::thread a(write_x_then_y);
    std::thread b(read_y_then_x);
    a.join();
    b.join();
    assert(z.load()!=0);        ⑤
}
复制代码

如果y.load不在循环里,有可能会触发断言。

 

宽松次序(memory_order_relaxed)

采用宽松次序,name原子类型上的操作不存在同步关系,在单一线程内,同一个变量上的操作依然服从先行关系,但几乎不要求线程间存在任何次序关系。该内存次序的唯一要求是,在一个线程内,对相同变量的访问次序不得重新编排。对于给定的线程,一旦它见到某原子变量在某时刻持有的值,则该线程的后续读操作不可能读取相对更早的值。memory_order_relaxed次序无需任何额外的同步操作,线程间仅存的共有信息是每个变量的改动顺序。

 

例子:

复制代码
#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x_then_y()
{
    x.store(true,std::memory_order_relaxed);    ⇽---  ①
    y.store(true,std::memory_order_relaxed);    ⇽---  ②
}
void read_y_then_x()
{
    while(!y.load(std::memory_order_relaxed));    ⇽---if(x.load(std::memory_order_relaxed))    ⇽---++z;
}
int main()
{
    x=false;
    y=false;
    z=0;
    std::thread a(write_x_then_y);
    std::thread b(read_y_then_x);    
    a.join();
    b.join();
    assert(z.load()!=0);    ⇽---  ⑤
}
复制代码

 

因为在线程write_x_then_y()中,有可能执行顺序是先y.store(true),然后在线程等read_y_then_x执行完之后,再x.store(true)。所以断言可能为失败。

 

 

在读《深入理解linux内核》的时候,有了新的理解。所以再回头读一遍,这些接近底层的内容,还是要读底层的代码和相关书籍才能有更深的体会。

posted @   woodx  阅读(151)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
点击右上角即可分享
微信分享提示