C++内存序

Posted woodx

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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内核》的时候,有了新的理解。所以再回头读一遍,这些接近底层的内容,还是要读底层的代码和相关书籍才能有更深的体会。

C++前序遍历中序遍历后序遍历层序遍历

二叉树深度优先遍历:前序遍历、中序遍历、后序遍历
二叉树广度优先遍历:层序遍历

二叉树的遍历规则:

前序遍历:5 4 1 2 6 7 8
中序遍历:1 4 2 5 7 6 8
后序遍历:1 2 4 7 8 6 5
层序遍历:

以下以前序遍历为例:

1.确定递归函数的参数和返回值:因为要打印出前序遍历节点的数值,所以参数里需要传入vector在放节点的数值,除了这一点就不需要在处理什么数据了也不需要有返回值,所以递归函数返回类型就是void,代码如下:
void traversal(TreeNode* cur, vector& vec)

2.确定终止条件:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要要结束了,所以如果当前遍历的这个节点是空,就直接return,代码如下:
if (cur == NULL) return;

3.确定单层递归的逻辑:前序遍历是中左右的循序,所以在单层递归的逻辑,是要先取中节点的数值,代码如下:
vec.push_back(cur->val); // 中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右

前序遍历

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void traversal(TreeNode* cur,vector<int>& vec){  //必须用引用
        if(cur == nullptr)  return;
        vec.push_back(cur->val);    //中
        traversal(cur->left,vec);   //左
        traversal(cur->right,vec);  //右
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root,result);
        return result;
    }
};

中序遍历

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void traversal(TreeNode* cur,vector<int>& vec){   //&是引用,相当于传地址。如果传值就什么也得不到
        if(cur == nullptr)  return;
        traversal(cur->left,vec);   //左
        vec.push_back(cur->val);    //中
        traversal(cur->right,vec);  //右
    }
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root,result);
        return result;
    }
};

后序遍历

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void traversal(TreeNode* cur,vector<int>& vec){
        if(cur == nullptr) return;
        traversal(cur->left,vec);    //左
        traversal(cur->right,vec);   //右
        vec.push_back(cur->val);     //中
    }
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root,result);
        return result;
    }
};

以上是关于C++内存序的主要内容,如果未能解决你的问题,请参考以下文章

双检查锁失效

盲猜原子变量内存屏障内存模型锁之间的关系

#yyds干货盘点#Android C++系列:Linux Socket编程预备知识

C++前序遍历中序遍历后序遍历层序遍历

c++接收数据的时候,需要把网络字节序转成主机字节序吗

c++ 二叉树遍历