根据cppreference,硬件可能要求 an 引用的对象atomic_ref<T>比其他T对象具有更严格的对齐方式,并且 an 上的操作是否atomic_ref是无锁的可以取决于引用对象的对齐方式。
为什么只需要引用的对象具有atomic_ref适当的对齐方式,而std::atomic似乎没有强加这一要求?
我想知道经典的“原子计数器动态调度”惯用法的正确内存顺序是什么。那是:
i使用 fetch-add 获取下一个要处理的元素的索引i超出数组末尾,则终止i线程安全地处理元素,因为没有其他线程可以拥有i例如:
#include <atomic>
std::atomic_int counter = 0;
void foo(int *data, int size) {
// we could also write counter++
for (int i; (i = counter.fetch_add(1, std::memory_order::seq_cst)) < size;) {
data[i] *= 2;
}
}
Run Code Online (Sandbox Code Playgroud)
// driver code
#include <thread>
#include <numeric>
#include <cassert>
int main() {
int data[1'000'000];
std::iota(std::begin(data), std::end(data), 0);
std::thread other{foo, data, std::size(data)};
foo(data, std::size(data));
other.join();
for (int i = 0; i < std::size(data); ++i) …Run Code Online (Sandbox Code Playgroud) 我正在阅读cppreference.com 上的std::memory_order和C++ 20 规范。我找不到术语“获取操作”和“释放操作”的定义。
第一个文档递归地定义了这些术语,表示“具有 memory_order_acquire 或更强的原子加载是一个获取操作”。和“memory_order_acquire:使用此内存顺序的加载操作在受影响的内存位置上执行获取操作。”
第二份文件似乎使用了这些术语,但没有任何定义。它开始使用这些术语,表示“一个或多个内存位置上的同步操作要么是消耗操作、获取操作、释放操作,要么是获取和释放操作。”
所以我用谷歌搜索并找到了LINUX KERNEL MEMORY BARRIERS,它清楚地定义了如下术语。
(5) 获取操作。
这起到了单向渗透屏障的作用。它保证 ACQUIRE 操作之后的所有内存操作看起来都发生在相对于系统的其他组件的 ACQUIRE 操作之后。ACQUIRE 操作包括 LOCK 操作以及 smp_load_acquire() 和 smp_cond_load_acquire() 操作。
在 ACQUIRE 操作之前发生的内存操作可能看起来是在 ACQUIRE 操作完成之后发生的。
ACQUIRE 操作几乎总是与 RELEASE 操作配对。
这个定义与作者对术语“获取操作”的含义完全相同吗?
如果我有一个原子变量,例如,
std::atomic<int> x;
Run Code Online (Sandbox Code Playgroud)
我想对它执行读写操作,我可以使用"普通"语法,例如,
std::cout << x; // read from x
x = 5; // write to x
Run Code Online (Sandbox Code Playgroud)
我也可以使用显式load和store成员函数:
std::cout << x.load(); // read from x
x.store(5); // write to x
Run Code Online (Sandbox Code Playgroud)
我见过Andrei Alexandrescu和Anthony Williams等人的建议只使用了表达load和store形式,大概是因为"正常"形式并没有强调变量是原子的.这似乎就像一种匈牙利符号.是否有关于在读写原子时使用的语法的新兴惯例?
如果使用std::atomic模板声明变量,例如std::atomic<int>,是否保证通过方法访问std::atomic将导致一致的值(即通过std::atomic方法写入的值),而不管顺序如何?
据我所知,这相当于询问读取或写入是否可以"撕裂" - 即在ISA级别可见的多个部分中写入或写入.
我试图atomic通过不同的线程更新变量,并得到此错误。这是我的代码。
class counter {
public:
std::atomic<int> done;
bool fn_write (int size) const {
static int count = 0;
if (count == size) {
done++;
count = 0;
return false;
} else {
count++;
return true;
}
}
};
int main() {
counter c1;
for (int i=0; i<50; i++) {
while (! c1.fn_write(10)) ;
}
}
Run Code Online (Sandbox Code Playgroud)
我在第8行中遇到以下错误done++。
错误:没有为后缀'++'声明'operator ++(int)'[-fpermissive]
我正在使用一些原子变量,所有unsigned int,我想将它们收集到一个结构中 - 实际上是一个POD.但是我也想要一个构造函数,因为我的编译器不是c ++ 11(所以我必须定义自己的构造函数来创建初始值).
所以最初我有:
// Names are not the real names - this is just for example
std::atomic<int> counter1;
std::atomic<int> counter2;
std::atomic<int> counter3;
Run Code Online (Sandbox Code Playgroud)
然后我很乐意根据需要增加/减少它们.但后来我决定再多一些计数器,因此将它们放入一个结构中:
struct my_counters {
int counter1;
int counter2;
int counter3;
// Constructor so that I can init the values I want.
my_counters(c1, c2, c3) : counter1(c1), counter2(c2), counter3(c3){;}
};
Run Code Online (Sandbox Code Playgroud)
但是因为我添加了一个自定义构造函数,所以这在技术上不再是POD.我正在阅读有关此问题的其他问题,他们说使用std :: atomic我需要一个POD,但我读到的其他问题表明结构需要是可复制的或者一些这样的...无论如何,我感到很困惑,我想要要知道我是否可以安全地使用我的结构my_counters作为原子类型:
std::atomic<my_counters> counters;
Run Code Online (Sandbox Code Playgroud)
然后在各种线程内:
// Are these operations now still atomic (and therefore safe to use across threads):
counters.counter1++;
counters.counter2--;
counters.counter3 …Run Code Online (Sandbox Code Playgroud) 原始问题:
我得到了一个结构数组,并在主线程中读取它时将其填充到一个单独的线程中:
struct DataModel MyData[1024];
struct DataModel
{
bool IsFilled;
float a;
float b;
}
Run Code Online (Sandbox Code Playgroud)
我有一个Thread,它将Mydata数组从0索引填充到最后一个索引(在上面是1024).
然后我从填充线程中获取最后一个填充的结构索引.
然后我尝试读取元素的值,其中一个索引低于填充的索引.
我们假设当第500个元素被填充时,我从MyData数组的499元素中读取值,所以我确保我没有读取正在写入的数组元素.
Q1:这个线程安全吗?
Q2:是否有可能发生未定义的行为或误读vales?
进一步编辑:
问题是编辑不当以添加更多细节,这就是为什么它引入了不一致的答案,所以我分开了以前的编辑,以提高答案和接受答案的一致性.
编辑1:这是可能实施的建议.虽然它可能显示错误的结果,但只是我想询问线程安全和未定义的行为,以下解决方案可能会显示各种结果,但我试图首先询问线程安全性.
std::atomic<int> FilledIndex;
void FillingMyData(struct DataModel myData[])
{
for(size_t i = 0; i < 1024; i++)
{
myData[i].a = rand();
myData[i].b = rand();
myData[i].IsFilled = true;
FilledIndex = i;
}
}
int main()
{
std::thread ReadThread(FillingMyData, MyData);
while(FilledIndex < 1024)
{
std::cout << MyData[FilledIndex].a;
}
ReadThread.join();
return 0;
}
Run Code Online (Sandbox Code Playgroud) 这是有关C ++标准的形式保证的问题。
该标准指出,std::memory_order_relaxed原子变量规则允许“凭空” /“出乎意料”的值出现。
但是对于非原子变量,这个例子可以有UB吗?是否r1 == r2 == 42有可能在C ++抽象机?== 42最初都不是变量,因此您不希望任何if主体执行,这意味着不会写入共享变量。
// Global state
int x = 0, y = 0;
// Thread 1:
r1 = x;
if (r1 == 42) y = r1;
// Thread 2:
r2 = y;
if (r2 == 42) x = 42;
Run Code Online (Sandbox Code Playgroud)
上面的示例改编自标准,该标准明确表示原子对象规范允许这种行为:
[注意:在以下示例中,要求确实允许r1 == r2 == 42,而x和y最初为零:
Run Code Online (Sandbox Code Playgroud)// Thread 1: r1 = x.load(memory_order_relaxed); if (r1 == 42) y.store(r1, memory_order_relaxed); // …
我有一个小类,它使用std :: atomic进行无锁操作。由于该课程被广泛调用,因此影响了性能,并且遇到了麻烦。
该类类似于LIFO,但是一旦调用pop()函数,它仅返回其环形缓冲区的最后写入元素(仅当自上次pop()之后存在新元素时)。
一个线程正在调用push(),另一个线程正在调用pop()。
由于这占用了我的计算机时间太多,因此我决定进一步研究std :: atomic类及其memory_order。我已经阅读了很多StackOverflow以及其他来源和书籍中的memory_order帖子,但是我无法对不同的模式有一个清晰的了解。特别是,我在获取和释放模式之间挣扎:我也看不出为什么它们与memory_order_seq_cst不同。
memory_order_relaxed:在同一线程中,原子操作是即时的,但是其他线程可能无法立即看到最新的值,它们将需要一些时间才能被更新。编译器或OS可以自由地对代码进行重新排序。
memory_order_acquire / release:由atomic :: load使用。它防止重新排序之前存在的代码行(编译器/ OS可能在此行之后对其重新排序),并使用此线程或另一个线程中的memory_order_release或memory_order_seq_cst读取存储在此原子上的最新值。memory_order_release还可以防止对该代码重新排序之后的代码。因此,在获取/发布中,两者之间的所有代码都可以被OS改组。我不确定这是在同一线程还是不同线程之间。
memory_order_seq_cst:最容易使用,因为就像我们使用变量的自然写法一样,立即刷新其他线程加载函数的值。
template<typename T>
class LockFreeEx
{
public:
void push(const T& element)
{
const int wPos = m_position.load(std::memory_order_seq_cst);
const int nextPos = getNextPos(wPos);
m_buffer[nextPos] = element;
m_position.store(nextPos, std::memory_order_seq_cst);
}
const bool pop(T& returnedElement)
{
const int wPos = m_position.exchange(-1, std::memory_order_seq_cst);
if (wPos != -1)
{
returnedElement = m_buffer[wPos]; …Run Code Online (Sandbox Code Playgroud)