zoukankan      html  css  js  c++  java
  • 多线程问题与double-check

    在一个多线程程序中,如果共享资源同时被多个线程使用,就有可能会造成多线程问题,这主要取决于针对该资源的某项操作是否是线程安全的。例如,.Net中的dictionary就是一个完全线程不安全的数据结构,对于dictionary的插入、删除都有可能带来多线程问题,这主要是由于dictionary的内部实现结构会频繁的由于插入、删除操作而改变长度,这时,如果出现多线程问题,程序最可能抛出数组越界的Exception。特别的,对于WebService来讲,每一个请求都会生成一个thread/instance,因此就要特别注意多线程问题了。

    一般地,多线程问题常常发生于对于共享资源的同时使用。例如,对于类的成员变量的使用,对于全局静态变量的使用,而对于函数内部的局部变量而言,一般式不会存在多线程问题的,因为每个线程在调用一个特定的函数时,都会生成一份函数内部成员变量的副本,线程和线程之间是互不相干的。
    解决多线程问题,最常见的方式就是加锁,使得某一资源在同一时刻只能被一个线程所用,而其他线程则必须在被加锁的代码外等待,直到锁被解除,例如如下c#代码所示:

    1             lock (_lock)
    2             {
    3                 //do something to the shared resources.
    4             }

    下面说说double-check。
    多线程问题也常常和一种lazy-initialize的设计模式联系在一起。在这里就会慢慢引出double-check。lazy-initialize讲的是,对于一些特别复杂的对象,让程序在第一次调用它的时候再对它进行初始化,而且保证仅仅初始化一次。
    首先想到的设计是这样的:

     1     class A
     2     {
     3 
     4 
     5         private ComplexClass _result = null;
     6 
     7         public ComplexClass GetResult()
     8         {
     9             if (_result == null)
    10             {
    11                 _result = new ComplexClass();
    12             }
    13             return _result;
    14         }
    15     }

    但是这样有一个问题。ComplexClass的构造过程较长的话,当第一个线程还在进行ComplexClass构造的时候,_result可能是null,也可能指向了一个尚未初始化完成的对象。这样,要么两个线程初始化了两次ComplexClass,要么第二个线程会返回一个指向不完整对象的引用。所以,在这里需要用到一个锁,如下所示:

     1         ComplexClass GetResult()
     2         {
     3             lock (_lock)
     4             {
     5                 if (_result == null)
     6                 {
     7                     _result = new ComplexClass();
     8                 }
     9             }
    10             return _result;
    11         }

    这样,虽然多线程的问题解决了,但是每一次需要使用result时都会请求锁,而请求锁对程序的性能是有很大影响的,因此我们在lock的外面再加一层check:

     1         ComplexClass GetResult()
     2         {
     3             if (_result == null)
     4             {
     5                 lock (_lock)
     6                 {
     7                     if (_result == null)
     8                     {
     9                         _result = new ComplexClass();
    10                     }
    11                 }
    12             }
    13             return _result;
    14         }

    这样,对于所有初始化完成后的请求,就都不用请求锁,而是直接返回_result。
    但是还是存在一点问题。对于一些编程语言来说,_result = new ComplexClass();这句代码会使得_result指向一个部分初始化的对象。也就是说,当线程A在初始化ComplexClass时,线程B有可能会判断_result已经不是null了,而这时其实初始化尚未完成,这时线程B就直接返回了一个部分初始化的对象,会造成程序的崩溃。那么,这个问题怎么解决呢?一般的解决方法是在程序内部再加一个局部变量(标识变量)做一层缓冲:

     1         ComplexClass GetResult()
     2         {
     3             ComplexClass result;
     4             if (_result == null)
     5             {
     6                 lock (_lock)
     7                 {
     8                     if (_result == null)
     9                     {
    10                         result = new ComplexClass();
    11                         _result = result;
    12                     }
    13                 }
    14             }
    15             return _result;
    16         }

    这样,上面的问题就彻底解决了~

  • 相关阅读:
    VS2015使用scanf报错解决方案
    C++的标准模板库(STL)简介
    C++中常用特殊符号简介(& , * , : , :: , ->)
    C++中#include <xxx.h>和#include "xxx.h"的区别(尖括号和双引号的区别)
    C++中#include的工作原理
    opencv中Mat与IplImage,CVMat类型之间转换
    C++数据类型简析
    让你在DOS中任意切换目录
    七种Prolog解释器/编译器
    C++中引用(&)的用法和应用实例
  • 原文地址:https://www.cnblogs.com/fangte/p/double-check.html
Copyright © 2011-2022 走看看