C# 为什么这个程序在布尔条件变量并没有波动的情况下不进入无限循环?

C# 为什么这个程序在布尔条件变量并没有波动的情况下不进入无限循环?,c#,.net,multithreading,volatile,C#,.net,Multithreading,Volatile,我想了解何时需要将变量声明为volatile。为此,我编写了一个小程序,希望它进入无限循环,因为缺少条件变量的波动性。它并没有进入无限循环,并且在没有volatile关键字的情况下运行良好 两个问题: 我应该在下面的代码列表中做些什么更改,以便它绝对需要使用volatile C#编译器是否足够聪明,可以将变量视为易变变量—如果它看到一个变量是从不同的线程访问的 上述情况引发了更多的问题:) a。易变性只是一个暗示吗 b。在多线程上下文中,何时应该将变量声明为volatile c。对于线程安全类,

我想了解何时需要将变量声明为volatile。为此,我编写了一个小程序,希望它进入无限循环,因为缺少条件变量的波动性。它并没有进入无限循环,并且在没有volatile关键字的情况下运行良好

两个问题:

  • 我应该在下面的代码列表中做些什么更改,以便它绝对需要使用volatile

  • C#编译器是否足够聪明,可以将变量视为易变变量—如果它看到一个变量是从不同的线程访问的

  • 上述情况引发了更多的问题:)

    a。易变性只是一个暗示吗

    b。在多线程上下文中,何时应该将变量声明为volatile

    c。对于线程安全类,所有成员变量都应该声明为volatile吗?那是不是太过分了

    代码清单(重点是波动性而非线程安全性):

    谢谢。

    我不是C#并发方面的专家,但恐怕你的期望是不正确的。从不同线程修改非易失性变量并不意味着其他线程永远看不到更改。只是不能保证它何时(如果)发生。在您的情况下,它确实发生了(顺便说一句,您运行了程序多少次?),可能是由于精加工线程根据@Russell的评论刷新了它的更改。但是在现实生活中,包括更复杂的程序流、更多的变量、更多的线程,更新可能会在5秒之后发生,或者可能千分之一的情况下根本不会发生


    因此,在不观察任何问题的情况下运行程序一次,甚至一百万次,只能提供统计数据,而不是绝对证据

    volatile关键字是给编译器的一条消息,要求编译器不要对此变量进行单线程优化。 这意味着该变量可以由多线程修改。 这使得变量值在读取时最“新鲜”

    这里粘贴的代码是使用volatile关键字的一个很好的示例。 这段代码在没有'volatile'关键字的情况下工作并不奇怪。但是,当更多线程正在运行并且您对标志值执行更复杂的操作时,它的行为可能更不可预测

    您仅对可由多个线程修改的变量声明volatile。 我不知道它在C#中到底是怎样的,但我假设不能对那些通过读写操作(如递增)修改的变量使用volatile。Volatile在更改值时不使用锁。 因此,在volatile上设置标志(如上所述)是可以的,增加变量是不可以的-那么应该使用同步/锁定机制。

    首先,-这对我来说已经足够好了

    如果你真的想考虑volatile,你必须考虑内存限制和优化——由编译器、抖动和CPU决定

    在x86上,写操作是释放围栏,这意味着您的后台线程将把
    true
    值刷新到内存中

    所以,您要寻找的是循环谓词中
    false
    值的缓存。编译器或jitter可能会优化谓词,并且只对其求值一次,但我猜对于类字段的读取它不会这样做。CPU不会缓存
    false
    值,因为您正在调用包含围栏的
    Console.WriteLine

    此代码需要
    volatile
    ,并且在没有
    volatile的情况下永远不会终止。请阅读:

    static void Run()
    {
        bool stop = false;
    
        Task.Factory.StartNew( () => { Thread.Sleep( 1000 ); stop = true; } );
    
        while ( !stop ) ;
    }
    

    当后台线程将true赋值给成员变量时,会有一个释放界限,该值会写入内存,而另一个处理器的缓存会更新或刷新该地址

    Console.WriteLine
    的函数调用是一个完整的内存限制,其可能执行任何操作(编译器优化除外)的语义要求不缓存
    stop

    但是,如果删除对
    Console.WriteLine
    的调用,我发现该函数仍在停止

    我相信,在没有优化的情况下,编译器不会缓存从全局内存计算的任何内容。然后,
    volatile
    关键字是一条指令,它甚至不考虑将涉及变量的任何表达式缓存到编译器/JIT

    此代码仍然会暂停(至少对我来说,我使用的是Mono):

    这表明它不是阻止缓存的while语句,因为这表明即使在同一个expression语句中也没有缓存变量

    这种优化看起来对内存模型很敏感,内存模型依赖于平台,这意味着这将在JIT编译器中完成;它没有时间(或智能)查看/其他线程中变量的使用情况,因此无法阻止缓存


    也许微软不相信程序员能够知道何时使用volatile,于是决定解除他们的责任,然后Mono也照做了。

    试着像这样重写它:

        public void Start()
        {
            var thread = new Thread(() =>
            {
                Thread.Sleep(5000);
                stop = true;
            });
    
            thread.Start();
    
            bool unused = false;
            while (stop == false)
                unused = !unused; // fake work to prevent optimization
        }
    
    并确保您是在发布模式下运行,而不是在调试模式下运行。在发布模式下应用优化,这实际上会导致代码在缺少
    volatile
    的情况下失败

    编辑:关于
    易失性

    我们都知道,在程序生命周期中有两个不同的实体可以以变量缓存和/或指令重新排序的形式应用优化:编译器和CPU

    这意味着,您编写代码的方式与实际执行代码的方式之间甚至可能存在很大差异,因为指令可能会相互重新排序,或者读操作可能会被缓存在编译器认为是“速度提高”的缓存中

    大多数情况下,这是好的,但有时(特别是在多线程上下文中)会导致tro
    public void Start()
    {
        stop = false;
    
        var thread = new Thread(() =>
        {
            while(true)
            {
                Thread.Sleep(50);
                stop = !stop;
            }
        });
    
        thread.Start();
    
        while ( !(stop ^ stop) );
    }
    
        public void Start()
        {
            var thread = new Thread(() =>
            {
                Thread.Sleep(5000);
                stop = true;
            });
    
            thread.Start();
    
            bool unused = false;
            while (stop == false)
                unused = !unused; // fake work to prevent optimization
        }