c#内存模型是否保证持有锁的线程能够保证在任何其他线程先前保持相同锁定时看到所执行的所有更新?
我一直在阅读c#规范,但似乎无法找到相关的细节.
C++20 包括atomic<float>和的专门化atomic<double>。这里有人能解释一下这有什么实际用途吗?我能想象的唯一目的是,当我有一个线程在随机点异步更改原子双精度或浮点并且其他线程异步读取该值时(但易失性双精度或浮点实际上应该在大多数平台上执行相同的操作)。但这种需要应该是极其罕见的。我认为这种罕见的情况不能证明纳入 C++20 标准是合理的。
这可能是一个与语言无关的问题,但实际上我对 C++ 的情况感兴趣:如何用支持 MT 编程的 C++ 版本编写多线程程序,即具有内存模型的现代 C++,如何证明是正确的?
在旧的 C++ 中,MT 程序只是根据 pthread 语义编写,并根据 pthread 规则进行验证,这在概念上很简单:正确使用原语并避免数据竞争。
现在,C++ 语言语义是根据内存模型定义的,而不是根据原始步骤的顺序执行来定义的。(该标准还提到了“抽象机器”,但我不再明白它的含义。)
如何用非顺序语义证明 C++ 程序的正确性?一个程序没有一个接一个地执行原始步骤,怎么能推理出这个程序呢?
#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,这意味着按照总顺序,对 …
#include <thread>
#include <atomic>
#include <cassert>
std::atomic<bool> x = {false};
std::atomic<bool> y = {false};
std::atomic<int> z = {0};
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()
{
std::thread a(write_x);
std::thread b(write_y);
std::thread c(read_x_then_y);
std::thread d(read_y_then_x); …Run Code Online (Sandbox Code Playgroud) 1.4中有一个双重检查问题 http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html 它是否在以后的JDK中得到修复?
当我有一个std::condition_variable cond和一些bool flag我可以使用谓词等待它:
cond.wait_for(some_lock, std::chrono::milliseconds(100), { return flag; })
Run Code Online (Sandbox Code Playgroud)
现在我想知道:从技术上讲,C++只在C++ 11中得到了一个合适的多线程内存模型,并且flag在多线程上下文中访问变量基本上是未定义的.所以我应该声明它std::atomic<bool>来逃避这种未定义的行为,对吧?
我特别想知道:如果我没有声明它std::atomic,那么我是否会一直读取陈旧的值,flag因为更新永远不会进入主内存?或者这是"理论上是,但实际上从未发生过"的情况?
C或C ++语言语义与用户形成矛盾。一些构造对它们的行为没有任何限制,这是由于在某些情况下(例如,取消引用未指向对象的指针,例如空指针)没有指定的行为,或者是通过明确地未定义。在任何一种情况下,都不能保证以下行为。
但是过去呢?这些已定义行为并产生输出的指令。我想可以删除输出,但是以前的交互可能在过去已经观察到。
未定义的行为是否可以预先确定,从而不会产生某些输出?例如:
std::cout << "hello, world" << std::endl; // with a flush
float f = 1./0.; // UB: cancels previous syscall?
Run Code Online (Sandbox Code Playgroud)
不会在这里进行writesyscall(假设是Unix)吗?
现在该内存模型如何?可以保证对原子对象,互斥体以及所有顺序一致的操作的所有操作均具有顺序(每个命令均与指令流一致,但不一定要有并集);如果程序表现出未定义的行为,什么时候适用保证?
在程序执行的某个时刻,实现可以使用未定义的行为作为不遵守内存模型要求的借口吗?换句话说,语言语义(用户)的合同客户可以在何时实现这些要求(在I / O上,在操作顺序上)?
(我意识到我可能没有我想要的那么具体。)
一些其源代码违反一致性或健全性规则的程序:
被描述为完全无效。编译器可以通过诊断拒绝这些程序,也可以对其进行编译,但是在那种情况下,该程序的执行未定义行为。让我们将其称为“ 先验 UB”。
问题不是关于那些程序,而是关于结构良好的程序,这些程序至少可以在一段时间内执行良好的定义。
我在几个地方读到,宽松的排序可以生成唯一的 ID。我对此表示怀疑,因为如果两个线程同时调用:
uniqueId.fetch_add(1, std::memory_order::relaxed);
那么线程 A 递增的值可能对线程 B 不可见。这意味着,两个线程可以获得相同的唯一 ID。
出于这个原因,我宁愿使用std::memory_order::acq_rel
你怎么认为?
在实践中无法测试。
假设我们有以下代码
int test(bool* flag, int* y)
{
if(*y)
{
*flag = false;
else
{
*flag = true;
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,编译器可以在这里证明写入标志总是会发生,所以我认为以下一个是允许的(我认为这根本不是优化,但只是为了示例)
int test(bool* flag, int* y)
{
*flag = true;
if(*y)
{
*flag = false;
}
}
Run Code Online (Sandbox Code Playgroud)
所以现在,我们也将 true 写入 flag if y!=0,但从 as-if 规则的角度来看,这看起来是有效的。
但我仍然认为,这种优化很奇怪,假设*y = true总是这样,所以标志总是假的,所以如果其他线程读取标志变量,他可能会看到 true,尽管它永远不应该发生,所以它会破坏 as-如果统治?优化是否有效?
另外:非原子的情况很清楚,因为它是 UB,并且所有的赌注都取消了,但是如果标志是具有宽松排序的原子的,那么怎么办?
在 Java 应用程序中,如果对对象状态的访问发生在同一线程上(在最简单的情况下,在单线程应用程序中),则无需同步以强制更改的可见性/一致性,如发生在关系规范:
“两个动作可以通过先发生关系排序。如果一个动作先发生在另一个动作之前,那么第一个动作对第二个动作可见并在第二个动作之前排序。
如果我们有两个动作 x 和 y,我们写 hb(x, y) 来表示 x 发生在 y 之前。
如果 x 和 y 是同一线程的动作,并且 x 在程序顺序中排在 y 之前,则 hb(x, y)。”
但是现代架构是多核的,所以 Java 线程可以在任何给定时间在其中任何一个上执行(除非这不是真的并且 Java 线程被固定到特定的内核?)。因此,如果是这种情况,如果一个线程写入变量 x,将其缓存在 L1 缓存或 CPU 寄存器中,然后开始在另一个内核上运行,该内核先前访问过 x 并将其缓存在寄存器中,则该值是不一致的.. . 当线程从 CPU 上取下时是否有某种机制(隐式内存屏障)?
java concurrency synchronization memory-model memory-barriers
我在高层次上理解函数声明的作用:在文件的顶部声明它们,以便编译器知道您正在调用哪些函数.但是,编译器究竟做了什么呢?我知道函数是写在内存的文本部分,并期望参数由堆栈上的寄存器给出.功能原型是否也放在文本中?或者是前向声明只是链接编辑器用于将所有文件连接在一起的指示器,它们在最终产品中被"删除"了吗?或者编译器是否对它们做了其他事情?
我在网上看了一下,找不到一个好的资源,所以如果你们中的任何人都可以回答这个问题,或者给我一个概述这个特定现象的资源,那将非常感谢!
编辑
我认为这个问题存在误解,这是我的错误.我的问题是C编译器如何使用前向声明.从下面的答案看来,它是在将c代码转换为汇编时使用的.它是否正确?