• 双检锁技术


    最近公司的项目中发现一个编译优化导致的bug。同事叙述为CPU开启out-of-order execution优化时,是有bug的”。针对这个问题,比较好的优化方法如下:

    private static JobManager self;
    private static object asyncObj = new object();
     
    public static JobManager Instance
    {
        get
        {
            if (self == null)
            {
                lock (asyncObj)
                {
                    if (self == null)
                    {
                        // 正确的实现方法应该为: var temp = new JobManager(); Interlocked.Exchange(ref self, temp);
                        self = new JobManager();
                    }
                }
            }
            return self;
        }
    }

    这里需要解释一下:

    self = new JobManager()

    这句你的本意是为 JobManager 分配内存,调用构造器初始化字段,再将引用赋给 self ,即发布出来让其他线程可见。但是,那只是你一厢情愿的想法,编译器可能这样做:为JobManager 分配内存,将引用发布到(赋给)self,再调用构造器。然而,如果在将引用发布给 self 之后,调用构造器之前,另一个线程发现 self 不为 null,便开始使用JobManager对象,这时会发生什么?这个时候对象的构造器还没有执行结束!这是一个很难追踪的bug。

    从双检锁技术的角度来看,使用Interlocked.Exchange确实是最好的解决方案。但有两个问题,它该如何解决?
    1.速度是否够快?
    2.如果一个线程池线程在Monitor的线程同步构造上阻塞,线程池会创建另一个线程来保持CPU的“饱和”,而创建一个新线程的代价是很昂贵的,我们该如何避免这样的情况?
     
    试着跳出“lock+2次if”的框子,我们可以使用Interlocked.CompareExchange来解决上面的问题。下面是一个示例:
    internal sealed class MySingleton
    {
        private static MySingleton s_value = null;
        public static MySingleton GetMySingleton()
        {
            if (s_value != null) return s_value;
     
            MySingleton temp = new MySingleton();
            Interlocked.CompareExchange(ref s_value, temp, null);
            return s_value;
        }
    }

    虽然多个线程同时调用GetMySingleton,会创建2个或者更多的MySingleton对象,但没有被s_value引用的临时对象会在以后被垃圾回收。大多数应用程序很少会发生同时调用GetMySingleton的情况,所以不太可能出现创建多个MySingleton对象的情况。上述代码带来优势是很明显的,首先,它的速度是非常快,其次,它永不阻塞线程。这就解决了前面在双检锁技术中提出的问题。

     
    另外,在.net 4.0中提供了2个类型封装上述两种模式(双检锁技术、使用Interlocked.CompareExchange技术):
    泛型System.Lazy类和System.Threading.LazyInitializer类。下面是2个示例:
    public static void Main()
    {
        Lazy<string> s = new Lazy<string>(() => DateTime.Now.ToLongTimeString(), LazyThreadSafetyMode.PublicationOnly);
     
        Console.WriteLine(s.IsValueCreated);
        Console.WriteLine(s.Value);
        Console.WriteLine(s.IsValueCreated);
        Thread.Sleep(5000);
        Console.WriteLine(s.Value);
        Console.WriteLine(DateTime.Now.ToLongTimeString());
    }
     
    输出结果:

    public static void Main()
    {
        string name = null;
     
        LazyInitializer.EnsureInitialized(ref name, () => "Benjamin");
        Console.WriteLine(name);
     
        LazyInitializer.EnsureInitialized(ref name, () => "Yao");
        Console.WriteLine(name);
    }
    输出结果:

    其中枚举LazyThreadSafetyMode解释如下:
    public enum LazyThreadSafetyMode
    {
        None = 0,     //完全没有线程安全劫持(适合GUI应用程序)
        PublicationOnly = 1,      //使用Interlocked.CompareExchange技术
        ExecutionAndPublication = 2,     //使用双检锁技术
    }

    转载需注明
    转载自: 博客园 - 爱拼才会赢 - benjamin超人 http://www.cnblogs.com/BenjaminYao/
    分类: .Net技术
  • 相关阅读:
    跨平台GUIQt windows 开发环境安装配置(Eclipse CDT+ MinGW+QT) (转载)
    跨平台GUIQt windows 开发环境安装配置(VS2005+QT+IntegrationPlugin)(转载)
    跨平台GUIQt ACER Aspire on Linux 开发环境安装配置(QT + GCC ) (原创)
    移动视频监控(2)原型开发Symbian客户端进展。
    编程语言大串联(1)C#,Java,C++
    优化页面上的sql
    一个段错误调试
    查询数据库空间
    shell 批量替换多个文件中字符串
    用户组相关
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2584521.html
Copyright © 2020-2023  润新知