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++内存序的主要内容,如果未能解决你的问题,请参考以下文章