我可以直接对原子变量进行算术运算吗?
因为我发现 C 标准库提供了很多实用函数,例如atomic_fetch_add执行原子变量和非原子变量之间的加法。但是,我很好奇,由于变量是原子的,我可以直接对其进行算术运算吗?就像下面所示的代码一样:
#include <threads.h>
#include <stdio.h>
#include <stdatomic.h>
atomic_int i = 0;
int run(void* v) {
i += 100; // <- is this operaiton thread-safe?
// atomic_fetch_add(&i, 100);
printf("%d\n", i);
return thrd_success;
}
int main(void) {
thrd_t thread;
thrd_create(&thread, run, NULL);
thrd_join(thread, NULL);
return 0;
}
Run Code Online (Sandbox Code Playgroud) boost::shared_mutex或std::shared_mutex(C++ 17)可用于单个写入器,多个读取器访问.作为一项教育练习,我将一个使用自旋锁的简单实现放在一起并具有其他限制(例如公平策略),但显然不打算在实际应用中使用.
我们的想法是,如果没有线程持有锁,则互斥锁保持引用计数为零.如果> 0,则该值表示有权访问的读者数.如果为-1,则单个编写者可以访问.
这是一个没有数据竞争的正确实现(特别是使用的,最小的内存排序)吗?
#include <atomic>
class my_shared_mutex {
std::atomic<int> refcount{0};
public:
void lock() // write lock
{
int val;
do {
val = 0; // Can only take a write lock when refcount == 0
} while (!refcount.compare_exchange_weak(val, -1, std::memory_order_acquire));
// can memory_order_relaxed be used if only a single thread takes write locks ?
}
void unlock() // write unlock
{
refcount.store(0, std::memory_order_release);
}
void lock_shared() // read lock
{
int val;
do {
do …Run Code Online (Sandbox Code Playgroud) 该代码的行为是否定义明确?
#include <stdatomic.h>
const int test = 42;
const int * _Atomic atomic_int_ptr;
atomic_init(&atomic_int_ptr, &test);
const int ** int_ptr_ptr = &atomic_int_ptr;
printf("int = %d\n", **int_ptr_ptr); //prints int = 42
Run Code Online (Sandbox Code Playgroud)
我将原子类型的指针分配给非原子类型的指针(类型相同)。这是我对这个示例的看法:
标准中明确规定的区别const,volatile并restrict从预选赛_Atomic资格赛6.2.5(p27):
只要允许一种类型的原子版本以及该类型的其他合格版本,本标准就明确使用短语“原子,限定或不限定类型”。在没有具体提及原子的情况下,短语“合格或不合格类型”不包括原子类型。
合格类型的兼容性也定义为6.7.3(p10):
为了使两个合格的类型兼容,两个都应具有相同的合格类型的兼容类型;指定符或限定符列表中类型限定符的顺序不会影响指定的类型。
结合以上引用的引言,我得出结论,原子类型和非原子类型是兼容的类型。因此,应用简单分配规则6.5.16.1(p1)(emp.mine):
左操作数具有原子,合格或不合格的指针类型,并且(考虑到左操作数在左值转换后将具有的类型), 两个操作数都是指向 兼容类型的合格或不合格版本的指针,并且由左侧指向的类型具有全部右边指出的类型的限定词;
因此,我得出的结论是,行为已得到很好的定义(即使将原子类型分配给非原子类型也是如此)。
所有这些的问题是,应用上述规则,我们还可以得出结论:将非原子类型简单分配给原子类型也定义得很好,这显然是不正确的,因为我们atomic_store为此拥有专用的泛型函数。
假设该体系结构可以以无锁的方式为std :: atomic支持8字节标量。为什么标准库不为8字节以下的结构提供类似的专业化知识?
这种std :: atomic专门化的简单实现可以std::memcpy将结构序列化/反序列化为等效形式std::uintx_t,其中xstruct的宽度以位为单位(四舍五入为最接近的2的幂,大于或等于2的整数)。结构的宽度)。这将很好地定义,因为std :: atomic要求这些结构是可微复制的。
例如。https://godbolt.org/z/sxSeId,这里Something只有3个字节,但实现调用__atomic_load和__atomic_exchange都使用锁表。
使用当前的 C++ 编译器,您可以获得比 CPU 实际支持更大的原子支持。使用 x64,您可以拥有 16 字节的原子,但 std::atomic 也适用于更大的元组。看这段代码:
#include <iostream>
#include <atomic>
using namespace std;
struct S { size_t a, b, c; };
atomic<S> apss;
int main()
{
auto ref = apss.load( memory_order_relaxed );
apss.compare_exchange_weak( ref, { 123, 456, 789 } );
cout << sizeof ::apss << endl;
}
Run Code Online (Sandbox Code Playgroud)
对于我的平台,上面的 cout 总是打印 32。但是,如果没有互斥体,这些事务实际上是如何工作的呢?我从检查拆卸中没有得到任何线索。
如果我使用 MSVC++ 运行以下代码:
#include <atomic>
#include <thread>
#include <array>
using namespace std;
struct S { size_t a, b, c, d, e; };
atomic<S> apss;
int …Run Code Online (Sandbox Code Playgroud) 确切地说,我只需要将双倍增加一倍,并希望它是线程安全的.我不想使用互斥锁,因为执行速度会急剧下降.
使用基本seqlock的简化版本 ,gcc load(memory_order_seq_cst)在编译代码时重新排序原子上的非原子加载-O3.在使用其他优化级别进行编译或使用clang进行编译时(甚至打开O3),不会观察到此重新排序.这种重新排序似乎违反了应该建立的同步关系,我很想知道为什么gcc重新排序这个特定的负载,如果这甚至是标准允许的话.
考虑以下load功能:
auto load()
{
std::size_t copy;
std::size_t seq0 = 0, seq1 = 0;
do
{
seq0 = seq_.load();
copy = value;
seq1 = seq_.load();
} while( seq0 & 1 || seq0 != seq1);
std::cout << "Observed: " << seq0 << '\n';
return copy;
}
Run Code Online (Sandbox Code Playgroud)
在seqlock程序之后,这个阅读器旋转,直到它能够加载两个实例seq_,它们被定义为a std::atomic<std::size_t>,是偶数(表示编写器当前没有写入)并且相等(表示编写器没有写入value在两个负载之间seq_).此外,因为这些负载被标记为memory_order_seq_cst(作为默认参数),我会想象指令copy = value;将在每次迭代时执行,因为它不能在初始加载时重新排序,也不能在后者下面重新排序.
但是,生成的组件会value在第一次加载之前发出负载,seq_甚至在循环之外执行.这可能导致不正确的同步或撕裂的读取value不会被seqlock算法解决.另外,我注意到这只发生在 sizeof(value) …
我正在使用 C++ 原子重构一些代码。代码如下所示:
std::atomic<bool> someFlag{}; // This can be set to true using a public method
// ...
const bool cond1 { someFunction() };
const bool cond2 { otherFunction() };
if (someFlag.load())
{
someFlage.store(false);
if (cond1 && cond2)
{
performSomeAction();
}
}
Run Code Online (Sandbox Code Playgroud)
我目前正计划if像这样重写语句:
if (std::atomic_exchange(&someFlag, false) &&
cond1 && cond2)
{
performSomeAction();
}
Run Code Online (Sandbox Code Playgroud)
极其重要的是,在此if语句之后,someFlag变量设置为false。因此,我想,以确保调用atomic_exchange总是发生,无论价值cond1和cond2。无论优化设置如何,布尔表达式都是从左到右计算的,我能保证会是这种情况吗?
我正在努力处理 C11 标准的第 5.1.2.4 节,特别是发布/获取的语义。我注意到https://preshing.com/20120913/acquire-and-release-semantics/(以及其他)指出:
... 释放语义防止写入释放的内存重新排序与程序顺序之前的任何读取或写入操作。
因此,对于以下情况:
typedef struct test_struct
{
_Atomic(bool) ready ;
int v1 ;
int v2 ;
} test_struct_t ;
extern void
test_init(test_struct_t* ts, int v1, int v2)
{
ts->v1 = v1 ;
ts->v2 = v2 ;
atomic_store_explicit(&ts->ready, false, memory_order_release) ;
}
extern int
test_thread_1(test_struct_t* ts, int v2)
{
int v1 ;
while (atomic_load_explicit(&ts->ready, memory_order_acquire)) ;
ts->v2 = v2 ; // expect read to happen before store/release
v1 = ts->v1 ; // expect write …Run Code Online (Sandbox Code Playgroud) 我有一个简单的代码:
#include <atomic>
int main()
{
std::atomic<int> a = 0;
}
Run Code Online (Sandbox Code Playgroud)
这段代码可以在 GCC 11.1.0 和 -std=c++17 下正常编译,但在 -std=c++14 和 -std=c++11 时失败。
使用删除的函数 std::atomic::atomic(const std::atomic&)
这是为什么?在 C++17 类中std::atomic仍然没有复制构造函数。为什么此代码对 -std=c++17 有效?
当然,我知道首选样式是 use {},但我很好奇为什么上面的代码从 C++17 开始就可以很好地编译。
stdatomic ×10
c++ ×7
atomic ×3
c ×3
c++11 ×2
c++17 ×2
c11 ×1
concurrency ×1
copy-elision ×1
gcc ×1
lock-free ×1
memory-model ×1
mutex ×1
x86-64 ×1