考虑以下两种变体:
std::atomic<int> a;
a = 1;
int b = a;
Run Code Online (Sandbox Code Playgroud)
和
std::atomic<int> a;
a.store(1);
int b = a.load();
Run Code Online (Sandbox Code Playgroud)
我从文档中看到第二个是完全原子的,但我不明白何时应该使用哪个以及什么是细节上的差异.
我想知道是否有可能为任何"常见"架构(如x64或ARMv7/ARMv8)创建无锁,线程安全的共享指针.
在cppcon2014上关于无锁编程的讨论中,Herb Sutter提出了一个无锁定单链表的(部分)实现.实现看起来很简单,但它依赖于shared_ptr标准库中不存在或使用专用std::atomic...函数的原子实现.这一点尤为重要,因为单个push/pop调用可能会调用多个原子加载/存储和compare_exchange操作.
我看到的问题(我认为谈话中的一些问题是朝着相同的方向)是因为这是一个实际的无锁数据结构,那些原子操作本身就必须是无锁的.我不知道任何标准库实现std::atomic...的无锁功能 - 至少有一个简短的google/SO搜索 - 我也没有找到如何实现无锁专业化的建议std::atomic<std::shared_ptr>.
在我浪费时间之前,我想问:
std::atomic<std::shared_ptr>?对于所提到的队列,它尤其需要CAS操作.供参考,这里是来自Herb Sutter的代码(可能包含来自我的错别字):
template<class T>
class slist {
struct Node { T t; std::shared_ptr<Node> next; };
std::atomic<std::shared_ptr<Node>> head;
public:
class reference{
std::shared_ptr<Node> p;
public:
reference(std::shared_ptr<Node> p_){}
T& operator*(){ return p->t; }
T* operator->(){ return &p->t; }
};
auto find(T t) const {
auto p = head.load();
while …Run Code Online (Sandbox Code Playgroud) 我有一个关于GCC-Wiki文章的问题.在标题"总结"下,给出了以下代码示例:
线程1:
y.store (20);
x.store (10);
Run Code Online (Sandbox Code Playgroud)
线程2:
if (x.load() == 10) {
assert (y.load() == 20)
y.store (10)
}
Run Code Online (Sandbox Code Playgroud)
据说,如果释放所有商店并获得所有负载,则线程2中的断言不会失败.这对我来说很清楚(因为线程1中x的存储与线程2中x的加载同步).
但现在出现了我不理解的部分.还有人说,如果所有商店都被释放并且消耗了所有负载,那么结果是相同的.来自y的负载是否可能在x的负载之前被提升(因为这些变量之间没有依赖关系)?这意味着线程2中的断言实际上可能会失败.
假设我们有以下代码来计算出现事件的次数:
int i=0;
void f() {
// do stuff . . .
if(something_happens) ++i;
}
int main() {
std::vector<std::thread> threads;
for(int j = 0; j< std::thread::hardware_concurrency(); ++j) {
threads.push_back(std::thread(f));
}
std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread_join));
std::cout << "i = " << i << '\n';
}
Run Code Online (Sandbox Code Playgroud)
目前看来我的竞争条件很明显.使用C++ 11,什么是(1)消除这种竞争条件的最简单方法,以及(2)最快的方法?,最好不使用互斥锁.谢谢.
更新:使用注释使用原子,我得到了一个工作程序,编译在英特尔编译器,版本13:
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <algorithm>
std::atomic<unsigned long long> i = 0;
void f(int j) {
if(j%2==0) {
++i;
}
}
int main() {
std::cout << "Atomic i = " …Run Code Online (Sandbox Code Playgroud) 在x86架构上,存储到同一内存位置的总订单有,例如,请参阅此视频.C++ 11内存模型有哪些保证?
更确切地说,在
-- Initially --
std::atomic<int> x{0};
-- Thread 1 --
x.store(1, std::memory_order_release);
-- Thread 2 --
x.store(2, std::memory_order_release);
-- Thread 3 --
int r1 = x.load(std::memory_order_acquire);
int r2 = x.load(std::memory_order_acquire);
-- Thread 4 --
int r3 = x.load(std::memory_order_acquire);
int r4 = x.load(std::memory_order_acquire);
Run Code Online (Sandbox Code Playgroud)
结果r1==1, r2==2, r3==2, r4==1是否允许(在x86以外的某些架构上)?如果我要更换所有memory_order的东西std::memory_order_relaxed怎么办?
我想编写可移植代码(Intel、ARM、PowerPC...)来解决一个经典问题的变体:
Initially: X=Y=0
Thread A:
X=1
if(!Y){ do something }
Thread B:
Y=1
if(!X){ do something }
Run Code Online (Sandbox Code Playgroud)
其中目标是避免两个线程都在做的情况something。(如果两者都没有运行也没关系;这不是只运行一次的机制。)如果您发现我下面的推理中有一些缺陷,请纠正我。
我知道,我可以通过memory_order_seq_cstatomic stores 和loads实现目标,如下所示:
std::atomic<int> x{0},y{0};
void thread_a(){
x.store(1);
if(!y.load()) foo();
}
void thread_b(){
y.store(1);
if(!x.load()) bar();
}
Run Code Online (Sandbox Code Playgroud)
这实现了目标,因为
{x.store(1), y.store(1), y.load(), x.load()}事件必须有一些单一的总顺序,它必须与程序顺序“边缘”一致:
x.store(1) “在TO之前” y.load()y.store(1) “在TO之前” x.load()如果foo()被调用,那么我们有额外的优势:
y.load() “之前读取值” y.store(1)如果bar()被调用,那么我们有额外的优势:
x.load() “之前读取值” x.store(1)所有这些边组合在一起将形成一个循环:
x.store(1)“在TO之前”“在TO之前y.load()读取值” y.store(1)“在TO之前” x.load()“读取之前值”x.store(true)
这违反了订单没有周期的事实。
我故意使用非标准术语“在 TO …
这是一个语言律师问题。
\n首先,a.wait()下面的代码是否总是返回?
std::atomic_int a{ 0 };\n\nvoid f()\n{\n a.store(1, std::memory_order_relaxed);\n a.notify_one();\n}\nint main()\n{\n std::thread thread(f);\n\n a.wait(0, std::memory_order_relaxed);//always return?\n\n thread.join();\n}\nRun Code Online (Sandbox Code Playgroud)\n我相信标准的目的是a.wait()总是得到回报。(否则atomic::wait/notify就没用了,不是吗?)但我认为目前的标准文本不能保证这一点。
标准的相关部分位于 \xc2\xa731.6 [atomics.wait] 第 4 段:
\n\n\n对原子对象上的原子等待操作的调用
\nM可以通过调用原子通知操作来解除阻塞M,如果存在副作用X,并且Y满足M以下条件:\n
\n- (4.1) \xe2\x80\x94 观察结果后,原子等待操作已阻塞
\nX,- (4.2) \xe2\x80\x94在,的修改顺序
\nX之前YM- (4.3) \xe2\x80\x94
\nY发生在调用原子通知操作之前。
和 \xc2\xa731.8.2 [atomics.types.operations] 第 29~33 段:
\n\n
void wait(T …
Herb Sutter 在他的“原子<>武器”演讲中展示了原子的几个示例用途,其中之一可归结为以下内容:(视频链接,带时间戳)
一个主线程启动多个工作线程。
工人检查停止标志:
while (!stop.load(std::memory_order_relaxed))
{
// Do stuff.
}
Run Code Online (Sandbox Code Playgroud)
主线程最终执行此操作stop = true;(注意,使用 order= seq_cst),然后加入工作线程。
Sutter 解释说,使用 order= 检查标志relaxed是可以的,因为谁在乎线程是否会因稍大的延迟而停止。
但为什么要stop = true;在主线程中使用呢seq_cst?幻灯片上说这是故意不这样做relaxed,但没有解释原因。
看起来它会起作用,可能会有更大的停止延迟。
这是性能和其他线程看到标志的速度之间的折衷吗?即,由于主线程仅设置标志一次,我们不妨使用最强的排序,以尽快传达消息?
在29.5原型类型的C++标准2014年11月的工作草案中,它指出:
- 有一个泛型类模板atomic.模板参数T的类型应该是可以轻易复制的(3.9).[注意:不能静态初始化的类型参数可能难以使用. - 尾注]
所以 - 据我所知 - 这:
#include <atomic>
struct Message {
unsigned long int a;
unsigned long int b;
};
std::atomic<Message> sharedState;
int main() {
Message tmp{1,2};
sharedState.store(tmp);
Message tmp2=sharedState.load();
}
Run Code Online (Sandbox Code Playgroud)
应该是完全有效的标准c ++ 14(以及c ++ 11)代码.但是,如果我没有libatomic手动链接,那么命令
g++ -std=c++14 <filename>
Run Code Online (Sandbox Code Playgroud)
给出 - 至少在Fedora 22(gcc 5.1)上 - 以下链接错误:
/tmp/ccdiWWQi.o: In function `std::atomic<Message>::store(Message, std::memory_order)':
main.cpp:(.text._ZNSt6atomicI7MessageE5storeES0_St12memory_order[_ZNSt6atomicI7MessageE5storeES0_St12memory_order]+0x3f): undefined reference to `__atomic_store_16'
/tmp/ccdiWWQi.o: In function `std::atomic<Message>::load(std::memory_order) const':
main.cpp:(.text._ZNKSt6atomicI7MessageE4loadESt12memory_order[_ZNKSt6atomicI7MessageE4loadESt12memory_order]+0x1c): undefined reference to `__atomic_load_16'
collect2: error: ld returned 1 exit status …Run Code Online (Sandbox Code Playgroud) 我正在浏览“原子操作库”,并遇到了原子“等待”和“notify_ ”方法的新 C++20 功能。我很好奇 std::condition_variable 的 'wait' 和 'notify_ ' 方法有何不同。
c++ ×10
stdatomic ×10
c++11 ×4
atomic ×3
c++20 ×2
concurrency ×2
memory-model ×2
c ×1
g++ ×1
libstdc++ ×1
lock-free ×1
shared-ptr ×1