c#变量新鲜度
本文关键字:新鲜度 变量 | 更新日期: 2023-09-27 17:53:40
假设类中有一个成员变量(具有原子读/写数据类型):
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。设置变量和Volatile时写入。阅读的时候阅读它。我看到很多这样写的代码(没有锁或volatile),我不确定它是否正确。
请注意,我的问题不是针对c#或。net的特定实现,它的目标是规范。我需要知道当前代码是否会在x86, x64, Itanium或ARM上运行。号我不关心m_Done何时被设置为true。我的问题是,我是否有c#语言规范和任务并行库的保证,最终m_Done将是真的,如果我从不同的线程访问它?
m_Done
的读是非易失性的,因此可以在时间上任意向后移动,并且结果可能被缓存。因此,可以观察到每次读取时都为false
。
我需要知道当前代码是否会在x86, x64, Itanium或ARM上运行。
规范不能保证代码在强(x86)和弱(ARM)内存模型上执行相同的操作。
规范对非易失性读写的保证非常清楚:在没有某些特殊事件(如锁)的情况下,它们可以在不同的线程上任意重新排序。
阅读规范以了解详细信息,特别是关于与volatile访问相关的副作用的部分。如果你有更多的问题,然后发布一个新的问题。这是非常棘手的事情。
此外,该问题假定您忽略了确定任务是否完成的现有机制,而是使用自己的机制。现有机制由专家设计;使用它们。
我看到很多这样写的代码(没有锁或volatile),我不确定它是否正确。
几乎可以肯定不是。
对于编写代码的人来说,一个很好的练习是:
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();
}
}
你有很好的机会看到它永远运行