C#变量新鲜度
C# variable freshness
假设我在 class 中有一个成员变量(具有原子 read/write 数据类型):
bool m_Done = false;
后来我创建了一个任务将其设置为 true:
Task.Run(() => m_Done = true);
我不关心什么时候 m_Done 会被设置为 true。
我的问题是 C# 语言规范和任务并行库是否可以保证
如果我从不同的线程访问它,最终 m_Done 会是真的吗?
示例:
if(m_Done) { // Do something }
我知道使用锁会引入必要的内存屏障,m_Done 稍后会显示为 true。
我也可以在设置变量时使用 Volatile.Write,在设置变量时使用 Volatile.Read
阅读它。
我看到很多代码都是这样写的(没有锁或 volatile),我不确定它是否正确。
请注意,我的问题不是针对 C# 或 .Net 的特定实现,
它是针对规范的。
我需要知道如果 运行 在 x86、x64、Itanium 或 ARM 上,当前代码的行为是否相似。
I don't care when exactly m_Done will be set to true. My question is do I have a guarantee by the C# language specification and the Task parallel library that eventually m_Done will be true if I'm accessing it from a different thread?
没有
m_Done
的读取是非易失性的,因此可能会及时向后移动任意远,结果可能会被缓存。结果,可以观察到每次读取都是 false
。
I need to know if the current code will behave similarly if running on x86, x64, Itanium or ARM.
规范不保证代码会在强 (x86) 和弱 (ARM) 内存模型上执行相同的操作。
规范非常清楚地说明了对非易失性读写的保证:在没有某些特殊事件(如锁)的情况下,它们可以在不同线程上任意重新排序。
阅读规范以获取详细信息,尤其是与易失性访问相关的副作用。如果在那之后您还有更多问题,那么 post 一个新问题。这是非常棘手的事情。
此外,该问题假定您忽略了确定任务已完成的现有机制,而是自己滚动。现有机制是由专家设计的;使用它们。
I'm seeing a lot of code written this way (without locks or volatile) and I'm not sure if it is correct.
几乎可以肯定不是。
向编写该代码的人提出的一个很好的练习是:
static volatile bool q = false;
static volatile bool r = false;
static volatile bool s = false;
static volatile bool t = false;
static object locker = new object();
static bool GetR() { return r; } // No lock!
static void SetR() { lock(locker) { r = true; } }
static void MethodOne()
{
q = true;
if (!GetR())
s = true;
}
static void MethodTwo()
{
SetR();
if (!q)
t = true;
}
字段初始化后,从一个线程调用 MethodOne,从另一个线程调用 MethodTwo。请注意,一切都是易变的,并且对 r 的写入不仅是易变的,而且是完全隔离的。两种方法都正常完成。之后是否有可能在第一个线程上观察到 s 和 t 都为真?在 x86 上可以吗?看来不是;如果第一个线程赢得比赛,则 t 保持为假,如果第二个线程获胜,则 s 保持为假;这种分析是错误的。为什么? (提示:x86 如何允许重写 MethodOne
?)
如果编码人员无法回答这个问题,那么他们几乎可以肯定无法使用 volatile 进行正确编程,并且不应该在没有锁的情况下跨线程共享内存。
试试这个代码,构建发布,运行 没有 Visual Studio:
class Foo
{
private bool m_Done = false;
public void A()
{
Task.Run(() => { m_Done = true; });
}
public void B()
{
for (; ; )
{
if (m_Done)
break;
}
Console.WriteLine("finished...");
}
}
class Program
{
static void Main(string[] args)
{
var o = new Foo();
o.A();
o.B();
Console.ReadKey();
}
}
你有机会看到它运行永远
假设我在 class 中有一个成员变量(具有原子 read/write 数据类型):
bool m_Done = false;
后来我创建了一个任务将其设置为 true:
Task.Run(() => m_Done = true);
我不关心什么时候 m_Done 会被设置为 true。
我的问题是 C# 语言规范和任务并行库是否可以保证
如果我从不同的线程访问它,最终 m_Done 会是真的吗?
示例:
if(m_Done) { // Do something }
我知道使用锁会引入必要的内存屏障,m_Done 稍后会显示为 true。 我也可以在设置变量时使用 Volatile.Write,在设置变量时使用 Volatile.Read 阅读它。 我看到很多代码都是这样写的(没有锁或 volatile),我不确定它是否正确。
请注意,我的问题不是针对 C# 或 .Net 的特定实现, 它是针对规范的。 我需要知道如果 运行 在 x86、x64、Itanium 或 ARM 上,当前代码的行为是否相似。
I don't care when exactly m_Done will be set to true. My question is do I have a guarantee by the C# language specification and the Task parallel library that eventually m_Done will be true if I'm accessing it from a different thread?
没有
m_Done
的读取是非易失性的,因此可能会及时向后移动任意远,结果可能会被缓存。结果,可以观察到每次读取都是 false
。
I need to know if the current code will behave similarly if running on x86, x64, Itanium or ARM.
规范不保证代码会在强 (x86) 和弱 (ARM) 内存模型上执行相同的操作。
规范非常清楚地说明了对非易失性读写的保证:在没有某些特殊事件(如锁)的情况下,它们可以在不同线程上任意重新排序。
阅读规范以获取详细信息,尤其是与易失性访问相关的副作用。如果在那之后您还有更多问题,那么 post 一个新问题。这是非常棘手的事情。
此外,该问题假定您忽略了确定任务已完成的现有机制,而是自己滚动。现有机制是由专家设计的;使用它们。
I'm seeing a lot of code written this way (without locks or volatile) and I'm not sure if it is correct.
几乎可以肯定不是。
向编写该代码的人提出的一个很好的练习是:
static volatile bool q = false;
static volatile bool r = false;
static volatile bool s = false;
static volatile bool t = false;
static object locker = new object();
static bool GetR() { return r; } // No lock!
static void SetR() { lock(locker) { r = true; } }
static void MethodOne()
{
q = true;
if (!GetR())
s = true;
}
static void MethodTwo()
{
SetR();
if (!q)
t = true;
}
字段初始化后,从一个线程调用 MethodOne,从另一个线程调用 MethodTwo。请注意,一切都是易变的,并且对 r 的写入不仅是易变的,而且是完全隔离的。两种方法都正常完成。之后是否有可能在第一个线程上观察到 s 和 t 都为真?在 x86 上可以吗?看来不是;如果第一个线程赢得比赛,则 t 保持为假,如果第二个线程获胜,则 s 保持为假;这种分析是错误的。为什么? (提示:x86 如何允许重写 MethodOne
?)
如果编码人员无法回答这个问题,那么他们几乎可以肯定无法使用 volatile 进行正确编程,并且不应该在没有锁的情况下跨线程共享内存。
试试这个代码,构建发布,运行 没有 Visual Studio:
class Foo
{
private bool m_Done = false;
public void A()
{
Task.Run(() => { m_Done = true; });
}
public void B()
{
for (; ; )
{
if (m_Done)
break;
}
Console.WriteLine("finished...");
}
}
class Program
{
static void Main(string[] args)
{
var o = new Foo();
o.A();
o.B();
Console.ReadKey();
}
}
你有机会看到它运行永远