我管理一些并发线程使用的内存,我有一个变量
无符号整数 freeBytes
当我从任务中请求一些记忆时
无符号整数字节需要
我必须检查是否
bytesNeeded<=freeBytes
如果是,则保留 freeBytes 的旧值并从 freeBytes bytesNeeded 中自动减去。
原子库或 x86 是否提供了这种可能性?
考虑以下简单的引用计数函数(与 一起使用boost::intrusive_ptr):
class Foo {\n // ...\n\n std::atomic<std::size_t> refCount_{0};\n\n friend void intrusive_ptr_add_ref(Foo* ptr)\n {\n ++ptr->refCount_; // \xe2\x9d\xb6\n }\n\n friend void intrusive_ptr_release(Foo* ptr)\n {\n if (--ptr->refCount_ == 0) { // \xe2\x9d\xb7\n delete ptr;\n }\n }\n};\nRun Code Online (Sandbox Code Playgroud)\n\n我仍在学习内存排序,我想知道在这种情况下fetch_add/sub( memory_order_seq_cst) 的默认内存排序是否太严格。由于我想确保的唯一顺序是在 \xe2\x9d\xb6 和 \xe2\x9d\xb7 之间,我认为我们可以将 \xe2\x9d\xb6 替换为
ptr->refCount_.fetch_add(1, std::memory_order_release);\nRun Code Online (Sandbox Code Playgroud)\n\n和 \xe2\x9d\xb7 与
\n\nif (ptr->refCount_.fetch_sub(1, std::memory_order_consume) == 1) {\nRun Code Online (Sandbox Code Playgroud)\n\n但内存排序对我来说仍然是新的和微妙的,所以我不确定这是否能正常工作。我错过了什么吗?
\nc++ multithreading reference-counting memory-model stdatomic
我阅读了 C++ 标准 (n4713) 的 § 32.6.1 3:
无锁的操作也应该是无地址的。也就是说,通过两个不同地址对同一内存位置的原子操作将进行原子通信。实现不应依赖于任何每个进程的状态。此限制允许通过多次映射到进程的内存和在两个进程之间共享的内存进行通信。
所以听起来可以在同一内存位置执行无锁原子操作。我想知道怎么做。
假设我在 Linux 上有一个命名的共享内存段(通过 shm_open() 和 mmap())。例如,如何对共享内存段的前 4 个字节执行无锁操作?
起初,我以为我可以只reinterpret_cast指向std::atomic<int32_t>*. 但后来我读到了这个。它首先指出 std::atomic 可能没有相同大小的 T 或对齐:
当我们设计 C++11 原子时,我误以为可以使用诸如
int x; reinterpret_cast<atomic<int>&>(x).fetch_add(1);
Run Code Online (Sandbox Code Playgroud)
如果 atomic 和 int 的表示不同,或者它们的对齐方式不同,这显然会失败。但我知道这在我关心的平台上不是问题。而且,在实践中,我可以通过在编译时检查大小和对齐方式匹配来轻松测试问题。
Tho,在这种情况下对我来说很好,因为我在同一台机器上使用共享内存,并且在两个不同的进程中投射指针将“获取”相同的位置。但是,文章指出编译器可能不会将强制转换的指针视为指向原子类型的指针:
然而,这不能保证是可靠的,即使在人们可能期望它工作的平台上,因为它可能会混淆编译器中基于类型的别名分析。编译器可能会假设 int 也不会作为
atomic<int>. (见 3.10,[Basic.lval],最后一段。)
欢迎任何输入!
Q1:我知道缓存一致性、存储缓冲区和失效队列是内存重新排序的根本原因吗?
存储释放是很好理解的,必须等待所有加载和存储完成才能将标志设置为 true。
关于加载获取,原子加载的典型用途是等待标志。假设我们有 2 个线程:
int x = 0;
std::atomic<bool> ready_flag = false;
Run Code Online (Sandbox Code Playgroud)
// thread-1
if(ready_flag.load(std::memory_order_relaxed))
{
// (1)
// load x here
}
// (2)
// load x here
Run Code Online (Sandbox Code Playgroud)
// thread-2
x = 100;
ready_flag.store(true, std::memory_order_release);
Run Code Online (Sandbox Code Playgroud)
编辑:在线程 1 中,它应该是一个 while 循环,但我复制了上面文章中的逻辑。因此,假设内存重新排序及时发生。
Q2 : 因为(1)和(2)取决于if条件,CPU必须等待ready_flag,这是否意味着write-release就足够了?在这种情况下,内存重新排序是如何发生的?
Q3:显然我们有load-acquire,所以我猜 mem-reorder 是可能的,那么我们应该把栅栏放在哪里,(1)还是(2)?
是如何atomic_flag实施的?在我看来,在 x86-64 上它atomic_bool无论如何都等效,但这只是一个猜测。x86-64 实现与 arm 或 x86 有什么不同吗?
我有两个std::atomic变量,如下所示:
std::atomic<bool> b1;
std::atomic<bool> b2;
Run Code Online (Sandbox Code Playgroud)
在代码中的某个时刻我需要交换它们。它在创建线程之前运行,所以我知道只有主线程,没有其他人试图读/写这些变量。但:
std::swap(b1, b2);
Run Code Online (Sandbox Code Playgroud)
这导致:
[...] MSVC\14.24.28314\include\utility(61,1): error C2280: 'std::atomic<bool>::atomic(const std::atomic<bool> &)': attempting to reference a deleted function
[...] MSVC\14.24.28314\include\atomic(1480): message : see declaration of 'std::atomic<bool>::atomic'
[...] MSVC\14.24.28314\include\atomic(1480,5): message : 'std::atomic<bool>::atomic(const std::atomic<bool> &)': function was explicitly deleted
Run Code Online (Sandbox Code Playgroud)
我不确定为什么复制构造函数被删除。所以我使用的解决方案是使用带有第三个变量的旧式交换:
const bool tmp = b1;
b1 = b2.load();
b2 = tmp;
Run Code Online (Sandbox Code Playgroud)
但现在我很好奇:为什么std::atomic删除了 的复制构造函数?
(实际代码比两个单个变量更复杂std::atomic<bool>,但我尝试将其简化为这个问题的简单情况。)
该atomic_compare_exchange_strong_explicit()函数采用两个memory_order参数success和failure(与 一样atomic_compare_exchange_weak_explicit())。取消选择 C11/C18 标准,我发现success和 的允许值为failure:
success = memory_order_relaxed failure = memory_order_relaxed
success = _release failure = _relaxed
success = _consume failure = _relaxed
or _consume
success = _acquire failure = _relaxed
or _acq_rel or _consume (?)
or _acquire
success = _seq_cst failure = _relaxed
or _consume (?)
or _acquire
or _seq_cst
Run Code Online (Sandbox Code Playgroud)
标准还说:
进一步地,如果比较为真,则按照成功的值影响内存,如果比较为假,则按照失败的值影响内存。这些操作是原子读-修改-写操作 (5.1.2.4)。
您的 ARM、POWER-PC 和其他“LL/SC”设备执行 Load-Link/Cmp/Store-Conditional 序列来实现atomic-cmp-exchange,其中 Load-Link 可能会也可能不会 _acquire,而Store-Conditional 则可能是 _acquire。可能是也可能不是 …
我在这篇SO帖子中阅读了 [[carries_dependency]] 。
但我无法理解的是接受的答案中的以下句子:
“特别是,如果将使用 memory_order_consume 读取的值传递给函数,然后没有 [[carries_dependency]],那么编译器可能必须发出内存栅栏指令以保证支持适当的内存排序语义。如果参数用 [[carries_dependency]] 注释,那么编译器可以假设函数体将正确携带依赖项,并且可能不再需要此围栏。
类似地,如果一个函数返回一个加载了 memory_order_consume 的值,或者从这样的值派生,那么如果没有 [[carries_dependency]],编译器可能需要插入一个栅栏指令来保证适当的内存排序语义得到支持。有了 [[carries_dependency]] 注释,这个围栏可能不再是必要的,因为调用者现在负责维护依赖树。”
让我们一步一步来:
“如果将使用 memory_order_consume 读取的值传递给函数,然后没有 [[carries_dependency]],那么编译器可能必须发出内存栅栏指令,以确保支持适当的内存排序语义。”
因此,对于释放-消耗内存模型中的原子变量,当原子变量作为参数传递给函数时,编译器将引入栅栏硬件指令,以便它始终具有提供给函数的原子变量的最新和更新值。
下一个 -
“如果参数用 [[carries_dependency]] 注释,那么编译器可以假设函数体将正确携带依赖项,并且可能不再需要这个围栏。”
这让我很困惑 - 原子变量值已经被消耗了,然后函数携带了什么依赖?
相似地 -
“如果一个函数返回一个加载了 memory_order_consume 的值,或者从这样的值派生,那么如果没有 [[carries_dependency]],编译器可能需要插入一个栅栏指令来保证适当的内存排序语义得到支持。使用 [[ Carry_dependency]] 注释,这个围栏可能不再是必要的,因为调用者现在负责维护依赖树。”
从这个例子中,它不清楚它试图说明携带依赖的意义是什么?
该程序有时会打印 00,但如果我注释掉 a.store 和 b.store 并取消注释 a.fetch_add 和 b.fetch_add ,它们执行完全相同的操作,即都设置 a=1,b=1 的值,我从不得到00。(在 x86-64 Intel i3 上测试,使用 g++ -O2)
我是不是遗漏了什么,或者按照标准“00”永远不会出现?
这是带有普通商店的版本,可以打印00。
// g++ -O2 -pthread axbx.cpp ; while [ true ]; do ./a.out | grep "00" ; done
#include<cstdio>
#include<thread>
#include<atomic>
using namespace std;
atomic<int> a,b;
int reta,retb;
void foo(){
//a.fetch_add(1,memory_order_relaxed);
a.store(1,memory_order_relaxed);
retb=b.load(memory_order_relaxed);
}
void bar(){
//b.fetch_add(1,memory_order_relaxed);
b.store(1,memory_order_relaxed);
reta=a.load(memory_order_relaxed);
}
int main(){
thread t[2]{ thread(foo),thread(bar) };
t[0].join(); t[1].join();
printf("%d%d\n",reta,retb);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
下面从不打印 00
// g++ -O2 -pthread axbx.cpp …Run Code Online (Sandbox Code Playgroud) c++ multithreading cpu-architecture memory-barriers stdatomic
我一直在阅读Jeff Preshing 的这篇关于 Synchronizes-With Relation 的文章,以及cpp 参考中的std::memory_order页面中的“Release-Acquire Ordering”部分,但我不太明白:
似乎标准有某种承诺,但我不明白为什么有必要。让我们以 CPP 参考中的示例为例:
#include <thread>
#include <atomic>
#include <cassert>
#include <string>
std::atomic<std::string*> ptr;
int data;
void producer()
{
std::string* p = new std::string("Hello");
data = 42;
ptr.store(p, std::memory_order_release);
}
void consumer()
{
std::string* p2;
while (!(p2 = ptr.load(std::memory_order_acquire)))
;
assert(*p2 == "Hello"); // never fires
assert(data == 42); // never fires
}
int main()
{
std::thread t1(producer);
std::thread t2(consumer);
t1.join(); t2.join();
}
Run Code Online (Sandbox Code Playgroud)
参考文献解释说:
如果线程 A 中的原子存储标记为 memory_order_release,并且线程 B …