- 主题:seq_cst 和 acq_rel 区别到底在哪
之前写了个缓存实现
template<typename T>
struct Cache {
std::atomic<T *> caches[4];
T *get() {
for (auto &o: caches) {
if (T *t = o.exchange(nullptr) return t;
}
return new T();
}
void release(T *t) {
for(auto &o: caches) {
t = o.exchange(t);
if (t == nullptr) return;
}
delete t;
}
};
一开始 exchange 使用的是 std::memory_order_acq_rel,感觉足矣,但是线上出现了野指针。改成默认的 seq_cst 就没问题了。
但是我看二者产生的指令是一样的呀,求问这二者具体区别在哪?我理解是访问多个 atomic 的话要使用 seq_cst?
--
FROM 114.251.196.*
原子操作就是有一天你觉得懂了,然后某一天又不懂了,还是用成熟的库,好,自己写的话,只敢上保守方案。
这个例子 看起来是 acq rel 好像就可以了,因为操作具体atomic变量的时候,并不依赖其他原子变量的取值,你的循环只是挨个检查而已。
--
FROM 14.154.51.*
seq_cst是全局顺序,所有线程看到的操作顺序都一致
acq_rel只是规定单个线程的顺序。
所以还要看是不是多线程调用这个类的。
--
FROM 221.218.167.*
acq-rel不是单个线程的序
原子变量A 在线程1 中release了,在线程2中,只要是使用了acquire,只要线程2的acquire动作是在线程1的release之后发生的,那一定能看到线程1中 原子变量A已经release出来的值
我觉得跟acq-rel 不能保证 store-load 不被乱序有关呢
【 在 z16166 的大作中提到: 】
: seq_cst是全局顺序,所有线程看到的操作顺序都一致
: acq_rel只是规定单个线程的顺序。
: 所以还要看是不是多线程调用这个类的。
--
修改:overcomeunic FROM 111.222.57.*
FROM 111.222.57.*
你说的对
这个最好是举两个线程操作这个cache的例子,找到race的地方
野指针也不一定是这个类的问题
get()返回的同一个指针有没可能被多个线程同时使用?
【 在 overcomeunic 的大作中提到: 】
: acq-rel不是单个线程的序
: 原子变量A 在线程1 中release了,在线程2中,只要是使用了acquire,只要线程2的acquire动作是在线程1的release之后发生的,那一定能看到线程1中 原子变量A已经release出来的值
: 我觉得跟acq-rel 不能保证 store-load 不被乱序有关呢
: ...................
--
修改:z16166 FROM 221.218.167.*
FROM 221.218.167.*
开了几个线程跑,没发现问题
template <typename T> struct LocklessCache {
std::atomic<T *> caches[4];
T *get() {
for (auto &o : caches) {
if (T *t = o.exchange(nullptr, std::memory_order_acq_rel))
return t;
}
return new T();
}
void release(T *t) {
for (auto &o : caches) {
t = o.exchange(t, std::memory_order_acq_rel);
if (t == nullptr)
return;
}
delete t;
}
};
LocklessCache<int> cache;
int main(int argc, char **argv) {
std::thread *threads[8];
for (size_t k = 0; k < _countof(threads); ++k) {
threads[k] = new std::thread([]() {
for (size_t k = 0; k < 1000000000; k++) {
auto p = cache.get();
//Sleep(1);
cache.release(p);
}
});
}
for (size_t k = 0; k < _countof(threads); ++k) {
threads[k]->join();
delete threads[k];
}
--
FROM 221.218.167.*
如果是代码中其它地方导致的data race,seq_cst应该也有才对
但一开始说从acq_rel -> seq_cst 就好了,这是疑惑的点
另外我好奇的是,acq_rel 比 seq_cst 好很多么性能上
【 在 z16166 的大作中提到: 】
: 你说的对
: 这个最好是举两个线程操作这个cache的例子,找到race的地方
: 野指针也不一定是这个类的问题
: ...................
--
修改:overcomeunic FROM 111.222.57.*
FROM 111.222.57.*
会不会因为分支预测错误导致乱序执行呢,seq_cst 会保证多个原子操作顺序一致,acq_rel 只会保证单个原子操作正确
【 在 Algoquant 的大作中提到: 】
: 原子操作就是有一天你觉得懂了,然后某一天又不懂了,还是用成熟的库,好,自己写的话,只敢上保守方案。
: 这个例子 看起来是 acq rel 好像就可以了,因为操作具体atomic变量的时候,并不依赖其他原子变量的取值,你的循环只是挨个检查而已。
--
FROM 114.251.196.*
: 但是我看二者产生的指令是一样的呀
你确定?? 要是汇编代码是完全一样,那么执行行为肯定是一模一样。
x86上是strong order,C++标准中规定的那些memory order大部分对于x86都是nonsense。给arm写程序的才需要关心这些细节。x86上只要保证原子性就够了,具体看编译后的汇编代码,对着Intel handbook对照着看。
--
FROM 61.172.164.*
嗯,实际上clang在armv8上可能只实现了一套,低版本clang通过ldaxr+stlxr+循环实现,高版本直接用STL里的方法
【 在 snnn 的大作中提到: 】
:
: 你确定?? 要是汇编代码是完全一样,那么执行行为肯定是一模一样。
: x86上是strong order,C++标准中规定的那些memory order大部分对于x86都是nonsense。给arm写程序的才需要关心这些细节。x86上只要保证原子性就够了,具体看编译后的汇编代码,对着Intel handbook对照着看。
: ...................
--
修改:zli07 FROM 114.251.196.*
FROM 114.251.196.*