• 优化你的DiscuzNT3.0,让它跑起来(6)在线人数和Regex.IsMatch()引发的hang


    注:本文仅针对 DiscuzNT3.0, sqlserver 2000版本,其他版本请勿对号入座。

    你没看错标题,的确是 在线人数和Regex.IsMatch()引发的hang。事情是这样的,就在今天我们的论坛出现的挂起问题,当时刚好赶上了抓dump文件。于是就有了今天这篇文章。 

    我们先用windbg看看论坛当时在干什么吧。

    1. 打开文件,运行 .load sos, 因为是hang,所以当然是要运行 !syncblk , 下面是运行结果:

    0:000> .load sos

    0:000> !syncblk
    Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner
      202 000fe87c            1         1 1c3d2c98   d28  48   02413704System.Collections.Generic.LinkedList`1[[System.Text.RegularExpressions.CachedCodeEntry, System]]
    -----------------------------
    Total           419
    CCW             2
    RCW             0
    ComClassFactory 0
    Free            354

     

    从上面看到,持有锁的线程是48 ,持有的对象是System.Collections.Generic.LinkedList 

     

    2. 们看看线程48在干什么, ~48s 切换到线程,!clrstack 看看执行的代码。

    0:000> ~48s
    eax=000006d7 ebx=1c3d2c98 ecx=000fe87c edx=044ec5c3 esi=00000354 edi=00000000
    eip=7c9585ec esp=1d8eeee4 ebp=1d8eef54 iopl=0         nv up ei pl zr na pe nc
    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
    ntdll!KiFastSystemCallRet:
    7c9585ec c3              ret
    0:048> !clrstack
    OS Thread Id: 0xd28 (48)
    ESP       EIP     
    1d8ef060 7c9585ec [GCFrame: 1d8ef060] 
    1d8ef0fc 7c9585ec [HelperMethodFrame: 1d8ef0fc] System.Threading.Monitor.Enter(System.Object)
    1d8ef150 7a4aed85 System.Text.RegularExpressions.Regex.LookupCachedAndUpdate(System.String)
    1d8ef180 7a4aec91 System.Text.RegularExpressions.Regex..ctor(System.String, System.Text.RegularExpressions.RegexOptions, Boolean)
    1d8ef1b4 7a4bf480 System.Text.RegularExpressions.Regex.IsMatch(System.String, System.String)
    1d8ef1c4 1ba6c3cc Discuz.Common.TypeConverter.StrToInt(System.String, Int32)
    1d8ef1e0 1ba6f6c2 Discuz.Common.TypeConverter.ObjectToInt(System.Object)
    1d8ef1e4 1d50a215 Discuz.Data.OnlineUsers.LoadSingleOnlineUser(System.Data.IDataReader)
    1d8ef1f4 1d50a111 Discuz.Data.OnlineUsers.GetOnlineUserCollection()
    1d8ef204 1d509f73 Discuz.Forum.OnlineUsers.GetOnlineUserCollection(Int32 ByRef, Int32 ByRef, Int32 ByRef, Int32 ByRef)
    1d8ef25c 1d30b291 Discuz.Web.website.ShowPage()
    1d8ef274 1ba6d837 Discuz.Forum.PageBase..ctor()
    1d8ef318 1d37e2d1 Discuz.Web.website..ctor()
    1d8ef324 1d37e0f0 ASP.aspx_2_website_aspx..ctor()
    。。。。。。这里省略若干字

    -------------------------- 

    我们从上面看到程序调用了 

    Discuz.Common.TypeConverter.StrToInt() 这个方法,然后进入 
    System.Text.RegularExpressions.dll,最后停留在
    System.Text.RegularExpressions.Regex.LookupCachedAndUpdate() 方法, 我们从dnt3.0的程序一步步来看看。
     1         /// <summary>
     2         /// 将对象转换为Int32类型
     3         /// </summary>
     4         /// <param name="str">要转换的字符串</param>
     5         /// <param name="defValue">缺省值</param>
     6         /// <returns>转换后的int类型结果</returns>
     7         public static int StrToInt(string str, int defValue)
     8         {
     9             if (string.IsNullOrEmpty(str) || str.Trim().Length >= 11 || !Regex.IsMatch(str.Trim(), @"^([-]|[0-9])[0-9]*(\.\w*)?$"))
    10                 return defValue;
    11 
    12             int rv;
    13             if (Int32.TryParse(str, out rv))
    14                 return rv;
    15 
    16             return Convert.ToInt32(StrToFloat(str, defValue));

    17         } 


    请出reflector 

    1 public static bool IsMatch(string input, string pattern)
    2 {
    3     return new Regex(pattern, RegexOptions.None, true).IsMatch(input);

    4 } 

    继续reflector

     1 private Regex(string pattern, RegexOptions options, bool useCache)
     2 {
     3     CachedCodeEntry cachedAndUpdate = null;
     4     string threeLetterWindowsLanguageName = null;
     5     if (pattern == null)
     6     {
     7         throw new ArgumentNullException("pattern");
     8     }
     9     if ((options < RegexOptions.None) || ((((int) options) >> 10!= 0))
    10     {
    11         throw new ArgumentOutOfRangeException("options");
    12     }
    13     if (((options & RegexOptions.ECMAScript) != RegexOptions.None) && ((options & ~(RegexOptions.CultureInvariant | RegexOptions.ECMAScript | RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase)) != RegexOptions.None))
    14     {
    15         throw new ArgumentOutOfRangeException("options");
    16     }
    17     if ((options & RegexOptions.CultureInvariant) != RegexOptions.None)
    18     {
    19         threeLetterWindowsLanguageName = CultureInfo.InvariantCulture.ThreeLetterWindowsLanguageName;
    20     }
    21     else
    22     {
    23         threeLetterWindowsLanguageName = CultureInfo.CurrentCulture.ThreeLetterWindowsLanguageName;
    24     }
    25     string[] strArray = new string[] { ((int) options).ToString(NumberFormatInfo.InvariantInfo), ":", threeLetterWindowsLanguageName, ":", pattern };
    26     string key = string.Concat(strArray);
    27     cachedAndUpdate = LookupCachedAndUpdate(key);
    28     this.pattern = pattern;
    29     this.roptions = options;
    30     if (cachedAndUpdate == null)
    31     {
    32         RegexTree t = RegexParser.Parse(pattern, this.roptions);
    33         this.capnames = t._capnames;
    34         this.capslist = t._capslist;
    35         this.code = RegexWriter.Write(t);
    36         this.caps = this.code._caps;
    37         this.capsize = this.code._capsize;
    38         this.InitializeReferences();
    39         t = null;
    40         if (useCache)
    41         {
    42             cachedAndUpdate = this.CacheCode(key);
    43         }
    44     }
    45     else
    46     {
    47         this.caps = cachedAndUpdate._caps;
    48         this.capnames = cachedAndUpdate._capnames;
    49         this.capslist = cachedAndUpdate._capslist;
    50         this.capsize = cachedAndUpdate._capsize;
    51         this.code = cachedAndUpdate._code;
    52         this.factory = cachedAndUpdate._factory;
    53         this.runnerref = cachedAndUpdate._runnerref;
    54         this.replref = cachedAndUpdate._replref;
    55         this.refsInitialized = true;
    56     }
    57     if (this.UseOptionC() && (this.factory == null))
    58     {
    59         this.factory = this.Compile(this.code, this.roptions);
    60         if (useCache && (cachedAndUpdate != null))
    61         {
    62             cachedAndUpdate.AddCompiled(this.factory);
    63         }
    64         this.code = null;
    65     }

    66 } 

    这个代码量较大,找到关键点 LookupCachedAndUpdate 

    private static CachedCodeEntry LookupCachedAndUpdate(string key)
    {
        
    lock (livecode)        
        {
    for (LinkedListNode<CachedCodeEntry> node = livecode.First; node != null; node = node.Next)
            {
                
    if (node.Value._key == key)
                {
                    livecode.Remove(node);
                    livecode.AddFirst(node);
                    
    return node.Value;
                }
            }
        }
        
    return null;

    } 

    终于找到这个lock的对象了,看看他是什么类型的,和我们通过windbg看到的一样吗

    internal static LinkedList<CachedCodeEntry> livecode;  

    果然一样, 这下应该放心了,就是这里的lock引起了hang,但是我们应该经常用Regex.IsMatch()的,也没见引起这个问题啊,为什么这里???

    我们看看在线人数有多少,如果在线人数比较多,访问的人数也多,那可能性就很大了,我们来看看到底有多少人在线。

    运行 !dso,看看本线程对象。

    0:048> !dso
    OS Thread Id: 0xd28 (48)
    ESP/REG  Object   Name
    1d8ef094 02413704 System.Collections.Generic.LinkedList`1[[System.Text.RegularExpressions.CachedCodeEntry, System]]
    1d8ef0b8 074b8c54 System.String    0:CHS:^([-]|[0-9])[0-9]*(\.\w*)?$
    1d8ef0bc 074b94ac System.String    114.247.10.127
    1d8ef0d0 074b94ac System.String    114.247.10.127
    1d8ef150 02413704 System.Collections.Generic.LinkedList`1[[System.Text.RegularExpressions.CachedCodeEntry, System]]
    1d8ef170 074b8c20 System.Text.RegularExpressions.Regex
    1d8ef184 0a3fcd1c System.String    ^([-]|[0-9])[0-9]*(\.\w*)?$
    1d8ef190 074b8c54 System.String    0:CHS:^([-]|[0-9])[0-9]*(\.\w*)?$
    1d8ef19c 074b8c08 System.String    -1
    1d8ef1a0 074b8c20 System.Text.RegularExpressions.Regex
    1d8ef1a4 0a3fcd1c System.String    ^([-]|[0-9])[0-9]*(\.\w*)?$
    1d8ef1b4 068e2dc4 Discuz.Common.Generic.List`1[[Discuz.Entity.OnlineUserInfo, Discuz.Entity]]
    1d8ef1b8 074b8c08 System.String    -1
    1d8ef1d4 074b8bac Discuz.Entity.OnlineUserInfo
    1d8ef1d8 068e2f64 System.Data.SqlClient.SqlDataReader
    。。。。。。省略若干字

    ----------------------

    找到上面的  

    Discuz.Common.Generic.List 的地址 
    068e2dc4 , 运行 !do 
    068e2dc4 
    0:048> !do 068e2dc4 
    Name: Discuz.Common.Generic.List`1[[Discuz.Entity.OnlineUserInfo, Discuz.Entity]]
    MethodTable: 1ceb7084
    EEClass: 1bb39dd0
    Size: 28(0x1c) bytes
     (C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\root\9932c999\b8dfff59\assembly\dl3\21614a1e\486ed665_6d88ca01\Discuz.Common.DLL)
    Fields:
          MT    Field   Offset                 Type VT     Attr    Value Name
    79124228  400099d        4      System.Object[]  0 instance 0f1b254c _items
    790fed1c  400099e        c         System.Int32  0 instance      472 _size
    790fed1c  400099f       10         System.Int32  0 instance      472 _version
    790f9c18  40009a0        8        System.Object  0 instance 00000000 _syncRoot
    79124228  40009a1        0      System.Object[]  0   shared   static _emptyArray
        >> Domain:Value dynamic statics NYI
     000e1008:NotInit dynamic statics NYI
     00107038:NotInit  <<
    790fed1c  400000c       14         System.Int32  0 instance        0 _fixedsize

    从上面的size可以看到在线人数是472人,就是说这个48这个线程要lock 472 次,如果有n个人访问那后面的人真的要等不少时候了。

    话说StrToInt()这个方法为什么要用Regex.IsMatch()呢,string 转换成 int 一般 int.TryParse()也足够了。不过从这里我才发现Regex.IsMatch() 里面原来还有个lock,不然还真不知道,也算是收获不小啊。

  • 相关阅读:
    正则表达式口诀
    Ajax请求的四种方式
    jQuery插件 -- jQuery UI插件
    电脑操作技巧
    递归
    声纹识别环境初次搭建
    视频编码book_实战_全角度——1
    SDK等阅读笔记
    音视频bug调试
    音视频开发进阶指南(二)
  • 原文地址:https://www.cnblogs.com/gezifeiyang/p/2090836.html
Copyright © 2020-2023  润新知