zoukankan      html  css  js  c++  java
  • CLR4.0线程无法结束问题分析

       今天在看social.msdn.microsoft.com论坛上面有个帖子提出了这个问题:

    The following peice of code is from C# 4.0 in a Nutshell. Ideally/logically this program should finish within 2-3 seconds, but it seems be caught in an infinite loop. Can someone explain why this happens and how one knows in advance that such a thing will happen for someother code that he/she has written. And who do you think is at fault here OS or .NET?(原始连接)

        其有疑问题的代码为:

    static void Main()
    {
        bool complete = false;
        var t = new Thread(() =>
        {
            //Console.WriteLine("in thread");
            bool toggle = false;
            while (!complete)
            {
                //Console.WriteLine("in loop");
                //Console.WriteLine(complete);
                toggle = !toggle;
            }
        });
        Console.WriteLine("starting thread");
        t.Start();
        Console.WriteLine("thread start");
        Thread.Sleep(1000);
        Console.WriteLine("setting complete");
        complete = true;
        Console.WriteLine("complete set");
        Console.WriteLine("Complete"+ complete);
        t.Join(); // Blocks indefinitely
        Console.WriteLine("done");
    }

        其中,代码是运行在4.0上的,根据代码的表面意思,1秒后complete的值会修改为true,然后导致循环的条件不满足,从而退出线程,而实际的结果却是线程无法退出,为什么哪?

        这段代码的表面意思虽然没错,但是,在CLR4.0的优化下(CLR 2.0的优化还没有如此强悍),有些隐藏意思被翻译出来,阻止了线程的退出,准确的说,是阻止了线程中循环条件变成false。

        在CLR看来,线程中有2个内存地址,一个是complete,另一个是toggle。其中对toggle有读取操作,也有写入操作,而对complete而言,只有读取操作。

        并且,由于没有给CLR任何Hint,CLR会认为这个complete不会被其它线程更改,因此,优化为使用寄存器来保存complete的值,除了第一次以外,以后就直接读取寄存器。

        这是一个很隐蔽的隐藏含义,但是这个隐藏含义却足以导致整个行为的改变。任何程序都无法修改另一个线程中的某个寄存器的值,因此,线程中的那个被优化了的complete永远为false,也就是最终的结果——线程无法退出。

        原楼主问到这个是错误是谁的错误,OS的还是.net的,这里不得不说,这个错误是写应用程序的人与写编译器的人的理解不一致导致的。

        写编译器的人会认为,凡是多线程修改的值,写应用程序的人都会给编译器一个Hint,从而只对有Hint的变量不使用优化;而写应用程序的人,却认为所有的变量都不会优化,于是悲剧就发生了。

        既然悲剧已经发生了,就要像办法阻止悲剧再次发生,那么这个编译器需要的Hint到底是什么?——volatile关键字(或内存同步机制,lock就是一种内存同步机制,但是不太可能仅仅为了读变量而去lock)

        volatile的直译是易变的,有点难理解是不是,举个例子吧:

        如果把某个变量声明为volatile的变量,那么,编译器(JIT)就知道这个变量是易变的,所以,任何读写操作都不能优化到寄存器中,必须读写内存地址。

        这样,就可以阻止悲剧发生了。

        什么时候用volatile?

        简单的说,变量会在线程外被更改的情况下,又没有使用lock或其他内存同步机制(Thread.MemoryBarrier等),就需要这个关键字帮忙了。

        其中,在线程外被更改的情况可以分为2种:

    • 变量会被其他线程更改
    • 变量会被硬件更改

        第一种比较好理解,第二种对于写应用程序级别的人来说,比较难理解,也不太会遇到,就死记硬背好了,呵呵。

  • 相关阅读:
    vue.js打包后,接口安全问题
    PHP开发api接口安全验证
    DOS命令操作 规格严格
    IIS 规格严格
    Swing语法高亮 规格严格
    Jetty 规格严格
    How to avoid “Illegal type in constant pool 规格严格
    数据库表产生类 规格严格
    利用Java编写简单IIS日志清理工具 规格严格
    语法高亮 规格严格
  • 原文地址:https://www.cnblogs.com/vwxyzh/p/1729416.html
Copyright © 2011-2022 走看看