在c#中我们真的需要VOLATILE关键字吗?



这是我在工作站上尝试的代码。

class Program
{
    public static volatile bool status = true;
    public static void Main()
    {
        Thread FirstStart = new Thread(threadrun);
        FirstStart.Start();
        Thread.Sleep(200);
        Thread thirdstart = new Thread(threadrun2);
        thirdstart.Start();
        Console.ReadLine();
    }
    static void threadrun()
    {
        while (status)
        {
            Console.WriteLine("Waiting..");
        }
    }
    static void threadrun2()
    {
        status = false;
        Console.WriteLine("the bool value is now made FALSE");
    }
}

正如你所看到的,我已经在Main中触发了三个线程。然后使用断点跟踪线程。我最初的概念是所有三个线程将同时被触发,但我的断点流显示线程执行流一个接一个地进行(输出格式也是如此,即从上到下执行线程)。为什么会这样呢?

另外,我尝试在声明中不使用volatile关键字的情况下运行相同的程序,我发现程序执行没有变化。我怀疑volatile关键字没有实际的生活用途。我哪里出错了吗?

你的思维方式有问题。

线程相关问题的本质是它们是不确定的。这意味着你所观察到的可能并不能预示未来会发生什么。

这就是为什么多线程编程是"困难的"。它经常违背特别的测试,甚至违背大多数单元测试。有效地做到这一点的唯一方法是了解整个软件和硬件堆栈,并通过使用状态机来绘制每一个可能发生的事件。

总而言之,线程编程不是关于已经发生的事情,而是关于可能发生的事情,不管发生的可能性有多大。

好吧,我试着用尽可能短的篇幅来解释一个很长的故事:

Number 1:尝试用调试器检查线程的行为,就像反复运行多线程程序并得出结论说它工作良好,因为在100次测试中没有失败一样有用:错误!线程的行为是完全不确定的(有些人会说随机的),你需要不同的方法来确保这样的程序能正确运行。

第2号: volatile的使用将变得清晰,一旦你删除它,然后在调试模式下运行你的程序,然后切换到发布模式。我想你会大吃一惊的……在Release模式下,编译器会优化代码(包括重新排序指令和缓存值)。现在,如果您的两个线程在不同的处理器内核上运行,那么执行正在检查status值的线程的内核将缓存其值,而不是重复检查它。另一个线程将设置它,但第一个线程永远不会看到更改:死锁!volatile防止了这种情况的发生。

在某种意义上,volatile是一个守卫,以防代码实际上没有(而且很可能不会)在多线程场景中运行。

您的简单代码不直接与volatile行为这一事实并不意味着什么。你的代码太简单了,与volatile没有任何关系。您需要编写非常计算密集型的代码来创建一个清晰可见的内存竞争条件。

此外,volatile关键字可能在其他平台上有用,而不是使用其他内存模型的x86/x64。(我的意思是,比如安腾)

Joe Duffy在他的博客上写了关于volatile的有趣信息。

然后使用断点跟踪线程。我最初的构想是所有的三个线程将同时被触发,但我的断点流显示线程执行流遵循一个断点然后是输出格式,即从上到下执行的线程)。伙计们,为什么会这样?

调试器暂时挂起线程,以便于调试。

我怀疑volatile关键字没有实际的实际用途。我要去吗?错误的地方吗?

Console.WriteLine调用很可能是修复掩盖问题。它们很可能隐式地为您生成必要的内存屏障。下面是一个非常简单的代码片段,它演示了当不使用volatile来声明stop变量时,实际上存在一个问题。

用Release配置编译以下代码,并在调试器外运行。

class Program
{
    static bool stop = false;
    public static void Main(string[] args)
    {
        var t = new Thread(() =>
        {
            Console.WriteLine("thread begin");
            bool toggle = false;
            while (!stop)
            {
                toggle = !toggle;
            }
            Console.WriteLine("thread end");
        });
        t.Start();
        Thread.Sleep(1000);
        stop = true;
        Console.WriteLine("stop = true");
        Console.WriteLine("waiting...");
        t.Join();
    }
}

最新更新