注:本文仅针对 DiscuzNT3.0, sqlserver 2000版本,其他版本请勿对号入座。
你没看错标题,的确是 在线人数和Regex.IsMatch()引发的hang。事情是这样的,就在今天我们的论坛出现的挂起问题,当时刚好赶上了抓dump文件。于是就有了今天这篇文章。
我们先用windbg看看论坛当时在干什么吧。
1. 打开文件,运行 .load sos, 因为是hang,所以当然是要运行 !syncblk , 下面是运行结果:
0:000> .load sos
从上面看到,持有锁的线程是48 ,持有的对象是System.Collections.Generic.LinkedList,
2. 我们看看线程48在干什么, ~48s 切换到线程,!clrstack 看看执行的代码。
--------------------------
我们从上面看到程序调用了
Discuz.Common.TypeConverter.StrToInt() 这个方法, 然后进入 System.Text.RegularExpressions.dll,最后停留在 System.Text.RegularExpressions.Regex.LookupCachedAndUpdate() 方法, 我们从dnt3.0的程序一步步来看看。
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
2 {
3 return new Regex(pattern, RegexOptions.None, true).IsMatch(input);
4 }
继续reflector
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
{
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,看看本线程对象。
----------------------
找到上面的
Discuz.Common.Generic.List 的地址 068e2dc4 , 运行 !do 068e2dc4
从上面的size可以看到在线人数是472人,就是说这个48这个线程要lock 472 次,如果有n个人访问那后面的人真的要等不少时候了。
话说StrToInt()这个方法为什么要用Regex.IsMatch()呢,string 转换成 int 一般 int.TryParse()也足够了。不过从这里我才发现Regex.IsMatch() 里面原来还有个lock,不然还真不知道,也算是收获不小啊。