一尘不染

C#线程可以真正缓存一个值并忽略其他线程对该值的更改吗?

c#

这个问题是 不是 关于种族条件,原子,或为什么你应该在你的代码中使用的锁。我已经知道这些了。

更新:我的问题不是“存在易失性内存的怪异现象”(我知道吗),我的问题是“ .NET运行时抽象没有那么远,所以您永远不会看到它”。

请参阅http://www.yoda.arachsys.com/csharp/threads/volatility.shtml,
以及有关字符串属性本身是否线程安全的第一个答案

(它们实际上是同一篇文章,因为一个引用另一个。)一个线程设置一个布尔值,另一个线程永远循环读取该布尔值-
那些文章声称读取线程可能会缓存旧值而从不读取新值,因此因此,您需要一个锁(或使用volatile关键字)。他们声称以下代码将永远循环下去。现在,我同意锁定变量是一种很好的做法,但是我不敢相信.NET运行时会真正忽略本文所述的内存值更改。我了解他们关于易失性存储器与非易失性存储器的讨论,并且我同意他们在
非托管 存储中有一个有效的论点 __代码,但我无法相信.NET运行时不会正确地将其抽象化,因此以下代码可以实现您所期望的。
该文章甚至承认该代码将“几乎可以肯定地”工作(尽管不能保证),因此我在声明中称呼BS。任何人都可以验证以下代码是否始终有效吗?有谁能在失败的情况下获得甚至一个案例(也许您不能总是复制它)?

class BackgroundTaskDemo
{
    private bool stopping = false;

    static void Main()
    {
        BackgroundTaskDemo demo = new BackgroundTaskDemo();
        new Thread(demo.DoWork).Start();
        Thread.Sleep(5000);
        demo.stopping = true;
    }

    static void DoWork()
    {
         while (!stopping)
         {
               // Do something here
         }
    }
}

阅读 472

收藏
2020-05-19

共1个答案

一尘不染

关键是:它可能会起作用,但不能 保证
该规范能起作用。人们通常所追求的是能够以正确的理由工作的代码,而不是通过编译器,运行时和JIT的偶然组合来工作,这些代码 可能会
在框架版本,物理CPU,平台以及x86与x64。

了解内存模型是一个非常非常复杂的领域,我并不声称自己是专家。但是在这方面 真正专家的人向我保证,您所看到的行为无法得到保证。

您可以根据需要发布任意数量的工作示例,但是不幸的是,除了“它通常可以工作”之外,它并没有得到太多证明。当然不能证明它可以 保证
正常工作。只需反驳一个例子,但发现这就是问题所在…

不,我没有手。


使用可重复的反例进行更新:

using System.Threading;
using System;
static class BackgroundTaskDemo
{
    // make this volatile to fix it
    private static bool stopping = false;

    static void Main()
    {
        new Thread(DoWork).Start();
        Thread.Sleep(5000);
        stopping = true;


        Console.WriteLine("Main exit");
        Console.ReadLine();
    }

    static void DoWork()
    {
        int i = 0;
        while (!stopping)
        {
            i++;
        }

        Console.WriteLine("DoWork exit " + i);
    }
}

输出:

Main exit

但仍在全CPU下运行;请注意,此时stopping已设置为true。的ReadLine是,这样的过程不会终止。优化似乎取决于循环内代码的大小(因此i++)。显然,它仅在“发布”模式下有效。添加volatile,一切正常。

2020-05-19