假设我有一个由100个字节组成的结构.我对以下代码有什么保证?
m_myLargeStruct = someValue; // copying 100 bytes
Thread.MemoryBarrier();
// Executed by another thread, after "Thread.MemoryBarrier" was called by the first thread
Console.WriteLine(m_myLargeStruct.ToString());
Run Code Online (Sandbox Code Playgroud)
内存模型是否保证在放置内存屏障后完成100字节的复制?或者内存屏障仅适用于处理器架构大小的类型?(32位为4字节,8位为64位).
这是volatile关键字仅适用于原始类型的原因吗?(如果我将一个8字节的成员声明为volatile,这意味着将使用一个互锁的instrinct来改变它的值?[因为32位机器上大于4字节的类型不能保证原子性]).
我希望我足够清楚.. :)
谢谢
在看到 Herb Sutters关于“原子武器”的精彩演讲后,我对放松原子的例子感到有些困惑。
我认为C++ 内存模型(SC-DRF = Sequentially Consistent for Data Race Free)中的原子在加载/读取时执行“获取”。
我知道对于负载 [和存储] 是默认值std::memory_order_seq_cst,因此两者是相同的:
myatomic.load(); // (1)
myatomic.load(std::memory_order_seq_cst); // (2)
Run Code Online (Sandbox Code Playgroud)
到目前为止一切都很好,没有涉及放松的原子(在听完演讲后,我永远不会使用放松的原子。永远。承诺。但当有人问我时,我可能不得不解释......)。
但是为什么我使用时它是“宽松”的语义
myatomic.load(std::memory_order_acquire); // (3)
Run Code Online (Sandbox Code Playgroud)
既然负载是获取而不是释放,为什么这与(1)和不同(2)?究竟是在这里放松?
我唯一能想到的就是我误解了load意味着acquire。如果这是真的,并且默认值seq_cst意味着两者,那不就意味着一个完整的围栏 - 没有任何东西可以传递该指令,也不能传递?我一定误解了那部分。
[并且对称地用于存储和释放]。
我写了一个多线程程序来演示英特尔处理器的乱序效果.该计划附在本文末尾.预期的结果应该是当handler1将x打印为42或0时.但是,实际结果总是为42,这意味着不会发生乱序效应.
我使用命令"gcc -pthread -O0 out-of-order-test.c"编译了程序.我在Intel IvyBridge处理器Intel(R)上运行Ubuntu 12.04 LTS(Linux内核3.8.0-29-通用)上的编译程序)Xeon(R)CPU E5-1650 v2.
有谁知道我应该怎么做才能看到乱序效果?
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int f = 0, x = 0;
void* handler1(void *data)
{
while (f == 0);
// Memory fence required here
printf("%d\n", x);
}
void* handler2(void *data)
{
x = 42;
// Memory fence required here
f = 1;
}
int main(int argc, char argv[])
{
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, handler1, NULL);
pthread_create(&tid2, NULL, handler2, NULL);
sleep(1);
return 0;
}
Run Code Online (Sandbox Code Playgroud) 考虑一下这段代码(从Simple-Web-Server中提取,但不需要知道库来回答这个问题):
HttpServer server;
thread server_thread;
server.config.port = 8080;
server.default_resource["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
string content = "Hello world!"
*response << "HTTP/1.1 200 OK\r\nContent-Length: " << content.size() << "\r\n\r\n" << content;
};
server_thread = thread([&server]() {
server.start();
});
Run Code Online (Sandbox Code Playgroud)
HttpServer::default_resource是一个std :: unordered_map,根据我的理解,它不是线程安全的. port是一张未签约的短片.
假设我的C++内存栅栏的理解是正确的,server通过新的线程中看到的,可能不是一个有效的状态为主线可能不会写的变化port,并default_resource从其他线程访问内存.因此,server.start()可能无法正常工作.
要解决这个问题,我必须通过添加到atomic_thread_fences 来更改代码:
HttpServer server;
thread server_thread;
server.config.port = 8080;
server.default_resource["GET"] = [](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
string content = "Hello world!"
*response …Run Code Online (Sandbox Code Playgroud) 我写这篇文章是为了深入理解 Java 中的 volatile
public class Main {
private int x;
private volatile int g;
public void actor1(){
x = 1;
g = 1;
}
public void actor2(){
put_on_screen_without_sync(g);
put_on_screen_without_sync(x);
}
}
Run Code Online (Sandbox Code Playgroud)
现在,我正在分析 JIT 为上述代码生成的内容。从我们在上一篇文章中的讨论中我们知道输出1, 0是不可能的,因为:
写挥发v的原因,每一个动作a前述v原因,那a之前是可见的(将被刷新到内存)v将是可见的。
.................(I removed not important body of method).....
0x00007f42307d9d5e: c7460c01000000 (1) mov dword ptr [rsi+0ch],1h
;*putfield x
; - package.Main::actor1@2 (line 14)
0x00007f42307d9d65: bf01000000 (2) mov edi,1h
0x00007f42307d9d6a: 897e10 (3) mov …Run Code Online (Sandbox Code Playgroud) 假设我让atomic<int> i;线程 A 使用 memory_order_release 执行原子存储/交换。接下来,线程 B 使用 memory_order_release 执行原子存储。线程 C 执行原子 fetch_add(0, memory_order_acquire);
线程 C 是否从线程A 和 B或仅从线程 B获取依赖项?
我有时会在关于内存排序的教程中看到术语"完全内存屏障",我认为这意味着以下内容:
如果我们有以下说明:
instruction 1
full_memory_barrier
instruction 2
Run Code Online (Sandbox Code Playgroud)
然后instruction 1不允许重新排序到下面full_memory_barrier,并且instruction 2不允许重新排序到上面full_memory_barrier.
但是完全内存屏障的反面是什么,我的意思是有什么像"半内存屏障"只能阻止CPU在一个方向上重新排序指令?
如果有这样的记忆障碍,我没有看到它的意思,我的意思是如果我们有以下指示:
instruction 1
memory_barrier_below_to_above
instruction 2
Run Code Online (Sandbox Code Playgroud)
假设这memory_barrier_below_to_above是一个阻止instruction 2重新排序到上面的内存屏障memory_barrier_below_to_above,因此不允许以下内容:
instruction 2
instruction 1
memory_barrier_below_to_above
Run Code Online (Sandbox Code Playgroud)
但是允许以下内容(这使得这种类型的内存屏障毫无意义):
memory_barrier_below_to_above
instruction 2
instruction 1
Run Code Online (Sandbox Code Playgroud) 我正在试验非时间指令,并且已经熟悉具有普通加载/存储的围栏如何操作。
Intel 定义了一个与非时间操作相关的内在,_mm_sfence,手册将其定义为:
保证每个前面的商店在任何后续商店之前都是全局可见的。
我对这个操作有一些疑问。
为什么我们需要定义两种具有相同实现的障碍?
#if defined(__x86_64) || defined(__i386__)
#define read_barrier() __asm__ __volatile__("":::"memory")
#define write_barrier() __asm__ __volatile__("":::"memory")
#else
Run Code Online (Sandbox Code Playgroud) TL;DR:在生产者-消费者队列中,放置一个不必要的(从 C++ 内存模型的角度来看)内存栅栏或不必要的强内存顺序是否有必要以牺牲可能更差的吞吐量为代价来获得更好的延迟?
C++ 内存模型是在硬件上执行的,方法是使用某种内存栅栏来实现更强的内存顺序,而不是将它们放在较弱的内存顺序上。
特别是,如果生产者这样做store(memory_order_release),而消费者使用 观察存储的值load(memory_order_acquire),则加载和存储之间没有围栏。在 x86 上根本没有栅栏,在 ARM 上栅栏是在存储之前和加载之后进行放置操作。
没有围栏存储的值最终会被没有围栏的负载观察到(可能在几次不成功的尝试之后)
我想知道在队列的两侧放置围栏是否可以更快地观察到值?如果有围栏和没有围栏,延迟是多少?
我希望只有一个循环load(memory_order_acquire)和pause/yield限制为数千次迭代是最好的选择,因为它无处不在,但想了解原因。
由于这个问题是关于硬件行为的,我希望没有通用的答案。如果是这样,我主要想知道 x86(x64 风格),其次是 ARM。
例子:
T queue[MAX_SIZE]
std::atomic<std::size_t> shared_producer_index;
void producer()
{
std::size_t private_producer_index = 0;
for(;;)
{
private_producer_index++; // Handling rollover and queue full omitted
/* fill data */;
shared_producer_index.store(
private_producer_index, std::memory_order_release);
// Maybe barrier here or stronger order above?
}
}
void consumer()
{
std::size_t private_consumer_index = 0;
for(;;)
{
std::size_t observed_producer_index = shared_producer_index.load( …Run Code Online (Sandbox Code Playgroud)