我知道 std::atomic 应该具有明确定义的行为,但我找不到这个问题的易于理解的在线答案:Do std::atomic.load() 和 .store() 有执行保证吗?
如果两个线程尝试对同一个 std::atomic 对象进行并发写入或读取,是否保证写入和读取都被执行?换句话说,写入或读取任务是否有可能根本无法完成?其中之一或两者会被阻止吗?或者它们保证是连续的?我不是在这里询问操作顺序。我只是简单地询问手术是否会在未来某个未指定的时间进行。
我正在使用 C++ 多处理,使用共享内存将数据从一个传递到另一个。我将一个数组放入共享内存中。进程A将数据复制到数组中,进程B将使用数组中的数据。然而,进程B需要知道数组中有多少项。
目前我正在使用管道/消息队列将数组大小从A传递到B。但我想我可能会在共享内存中放置一个原子变量(如atomic_uint64_t),在进程A中修改它并在进程B中加载它。但是我有以下问题。
从 C++20 std::atomics 开始,有等待和通知操作。使用 is_always_lock_free 我们可以确保实现是无锁的。使用这些积木构建无锁互斥锁并不那么困难。在简单的情况下,锁定将是比较交换操作,或者如果互斥体被锁定则等待。这里最大的问题是这是否值得。如果我可以创建这样的实现,那么 STL 版本很可能会更好、更快。然而,我仍然记得当我在 2016 年看到 QMutex 如何优于 std::mutex QMutex 与 std::mutex时,我是多么惊讶。那么您认为我应该尝试这样的实现还是 std::mutex 的当前实现已经足够成熟,可以进行远远超出这些技巧的优化?
更新 我的措辞不是最好的,我的意思是实现可以在快乐的路径上无锁(从未锁定状态锁定)。当然,如果我们需要等待获取锁,我们应该被阻塞并重新调度。在大多数平台上,atomic::wait 很可能不是通过简单的自旋锁实现的(现在让我们忽略极端情况),所以基本上它实现了 mutex::lock 所做的相同的事情。所以基本上,如果我实现这样一个类,它将执行与 std::mutex 完全相同的操作(同样在大多数流行平台上)。这意味着 STL 可以在支持这些技巧的平台上的互斥实现中使用相同的技巧。就像这个 spinlock,但我会使用原子等待而不是旋转。我应该相信我的 STL 实现是他们这样做的吗?
在java 17中,AtomicReference有compareAndExchange类似于 的方法compareAndSet,但它不返回布尔值,而是返回原子操作之前的值。我需要它来实现自定义并发结构。
由于项目的限制,我只能使用 Java 8 功能。一些挖掘揭示了VarHandle其中有compareAndExchange。但是,VarHandle需要 Java 9。
因此,看来我必须compareAndExchange亲自实施了。但如何利用现有方法高效地做到这一点呢?(那么compareAndExchangeWeak版本呢?)
(顺便说一句,我不能依赖任何第三方库)
#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x()
{
x.store(true,std::memory_order_seq_cst); // 1
}
void write_y()
{
y.store(true,std::memory_order_seq_cst); // 2
}
void read_x_then_y()
{
while(!x.load(std::memory_order_seq_cst)); // 3
if(y.load(std::memory_order_seq_cst)) // 4
++z;
}
void read_y_then_x()
{
while(!y.load(std::memory_order_seq_cst)); // 5
if(x.load(std::memory_order_seq_cst)) // 6
++z;
}
int main() {
x=false;
y=false;
z=0;
std::thread a(write_x);
std::thread b(write_y);
std::thread c(read_x_then_y);
std::thread d(read_y_then_x);
a.join();
b.join();
c.join();
d.join();
assert(z.load()!=0);
}
Run Code Online (Sandbox Code Playgroud)
在《C++ Concurrency in Action》一书中,作者在谈论顺序一致性时给出了这个例子,并说assert永远不能触发,因为
[1] 或 [2] 必须首先发生...并且如果一个线程看到 x==true 然后随后看到 y==false,这意味着按照总顺序,对 …
引用gnu:
实际上,您可以假设 int 是原子的。您还可以假设指针类型是原子的;非常方便。这两个假设在 GNU C 库支持的所有机器上以及我们所知的所有 POSIX 系统上都是成立的。
这怎么可能?我见过的与锁相关的所有示例都是用int计数器制作的,例如https://www.delftstack.com/howto/c/mutex-in-c/。
我认为atomic.Load(addr)应该等于*addr并且atomic.Store(addr, newval)应该等于*addr = newval。那么为什么这样做(使用*addror *addr = newval)不是原子操作呢?我的意思是它们最终会被解释为只是一条 cpu 指令(这是原子的)?
原子是否将可重复读取缓存在寄存器中?或者它们只是原子的,即读取可能不会分成多个部分?
MSVC++、clang++ / clang-cl 和 g++ 不会在没有内存排序的情况下缓存原子读取:
#include <atomic>
using namespace std;
int x( atomic_int const &ai )
{
int
a = ai.load( memory_order_relaxed ),
b = ai.load( memory_order_relaxed );
return a + b;
}
Run Code Online (Sandbox Code Playgroud)
克++:
movl (%rdi), %edx
movl (%rdi), %eax
addl %edx, %eax
ret
Run Code Online (Sandbox Code Playgroud)
铿锵-cl:
mov eax, dword ptr [rcx]
add eax, dword ptr [rcx]
ret
Run Code Online (Sandbox Code Playgroud)
CL:
mov edx, DWORD PTR [rcx]
mov eax, DWORD PTR [rcx]
add eax, edx
ret 0`
Run Code Online (Sandbox Code Playgroud) 目前正在使用 GCC 查看 C/C++ 中的原子操作,发现内存中自然对齐的全局变量具有原子读取和写入。
然而,我试图按位与一个全局变量,并注意到它归结为一个读取-修改-写入序列,如果有多个线程对该字节值进行操作,那么这会很麻烦。
经过一番研究,我选择了这两个例子:
C 示例- GCC 扩展__sync_fetch_and_and
#include <stdio.h>
#include <stdint.h>
uint8_t byteC = 0xFF;
int main() {
__sync_fetch_and_and(&byteC, 0xF0);
printf("Value of byteC: 0x%X\n", byteC);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
C++ 示例- 使用原子的 C++11fetch_and
#include <iostream>
#include <atomic>
std::atomic<uint8_t> byteCpp(0xFF);
int main() {
byteCpp.fetch_and(0xF0);
std::cout << "Value of byteCpp: 0x" << std::hex << static_cast<int>(byteCpp.load()) << std::endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
其他示例如下,但它们似乎不太直观且计算成本更高。
用一个pthread_mutex_lock
uint8_t byte = 0xFF;
pthread_mutex_t byte_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&byte_mutex);
byte …Run Code Online (Sandbox Code Playgroud) 我正在对广泛使用的开源库进行重构,并希望使其尽可能强大。
目前,如果支持的话,它会使用原子size_t变量,但我想知道它是否会错过一些不起眼的平台,例如,32 位原子同时具有 64 位指针。