Managed空间

彭刚的blog
随笔 - 26, 评论 - 153, 引用 - 81

导航

标签

每月存档

最新留言

广告

【第1页/共2页,30条】
首页
前页
1
2004年11月19日

http://blogs.msdn.com/bclteam/archive/2004/11/18/266136.aspx

posted on 2004-11-19 11:17:00 by gangp  评论(10) 阅读(3891)

 
2004年10月22日

New Article Posted at:

http://blog.joycode.com/gangp/articles/36225.aspx

posted on 2004-10-22 09:29:00 by gangp  评论(10) 阅读(4269)

 

关于lock的基本知识

托管对象的结构如下:

           m_SyncBlockValue

对象指针-> m_pMethodTable

           Data

 

在每个托管对象的开始是该对象类型的方法表。在方法表之前是m_SyncBlockValue

m_SyncBlockValue 的高6位用来标记m_SyncBlockValue的用途。SyncBlockValue 的低26位用来存储哈希码,SyncBlock索引或SpinLock

26位值的含义由高6位来决定。

 

lock一个对象时,CLR首先将m_SyncBlockValue当做一个SpinLock使用。

如果CLR在有限次的重试后无法获得SpinLock的拥有权,CLRSpinLock升级为一个AwareLockAwareLock类似与Windows中的CriticalSection

 

 

实例分析

这个例子是我今天在工作中遇到的一个Dump文件。

首先使用“~*kb”显示所有线程的调用堆栈,然后随便挑一个在等待托管锁的线程。

线程(70):

 

ChildEBP RetAddr  Args to Child             

0a21f11c 77f4372d 77e41bfa 00000001 0a21f16c SharedUserData+0x304

0a21f120 77e41bfa 00000001 0a21f16c 00000001 ntdll!NtWaitForMultipleObjects+0xc

0a21f1c8 7975e217 00000001 0019ae10 00000000 KERNEL32!WaitForMultipleObjectsEx+0x11a

0a21f1ec 796a1c79 00000001 0019ae10 00000000 mscorwks!Thread::DoAppropriateAptStateWait+0x6c

0a21f268 796a1ae0 00000001 0019ae10 00000000 mscorwks!Thread::DoAppropriateWaitWorker+0x14b

0a21f2b8 796e0772 00000001 0019ae10 00000000 mscorwks!Thread::DoAppropriateWait+0x43

0a21f2e0 796751ba ffffffff 00000001 00000000 mscorwks!CLREvent::WaitEx+0x8b

0a21f2f0 798c9aa4 ffffffff 00000001 00000000 mscorwks!CLREvent::Wait+0x17

0a21f368 798c9e20 07f02270 ffffffff 00000000 mscorwks!AwareLock::EnterEpilog+0x85

0a21f384 798ca083 07f02270 00b976f8 79651968 mscorwks!AwareLock::Enter+0x69

0a21f3e0 798679e4 0bbd1414 00b7480c 00b976f8 mscorwks!AwareLock::Contention+0x152

0a21f464 08200cee 00000000 00b7480c 0bbd1400 mscorwks!JITutil_MonContention+0x5d

。。。

 

首先要找出AwareLock的值。由于代码优化的原因,Windbg给出的参数值往往是错误的。

更可靠的方法是查看汇编代码:

JITutil_MonContention:

 

798679dc 8b4de8           mov     ecx,[ebp-0x18] ; ebp is 0a21f464

798679df e857250600       call    mscorwks!AwareLock::Contention (798c9f3b)

798679e4 834dfcff         or      dword ptr [ebp-0x4],0xffffffff

 

在上面的调用堆栈中有JITutil_MonContentionEBP值,所以我们可以得到AwareLock的地址

dd 0a21f464-0x18 L1

0a21f44c  0019adfc

 

dt mscorwks!AwareLock 0019adfc

   +0x000 m_MonitorHeld    : 101

   +0x004 m_Recursion      : 1

   +0x008 m_HoldingThread  : 0x07f18080

   +0x00c m_TransientPrecious : 50

   +0x010 m_dwSyncIndex    : 0x80000020

   +0x014 m_SemEvent       : CLREvent

   =79b05808 AwareLock::AllocLockCrst : CrstStatic

 

m_HoldingThread”记录持有该锁的线程。CLR使用一个全局数组来储存SyncBlockm_dwSyncIndex的值(低26位)是指向该数组的索引。

 

使用SOS命令“!threads”可以获得托管线程的信息:

42   4a  e94 07f18080   380b220 Enabled  00000000:00000000 00149ef0     1 MTA (Threadpool Worker)

 

切换到线程42 windbg命令“~42s”)。使用同样的步骤可以看出线程07f18080在等待如下的lock

dt mscorwks!AwareLock 0019adc8

   +0x000 m_MonitorHeld    : 3

   +0x004 m_Recursion      : 1

   +0x008 m_HoldingThread  : 0x07f02270

   +0x00c m_TransientPrecious : 1

   +0x010 m_dwSyncIndex    : 0x80000023 ( index into global array)

   +0x014 m_SemEvent       : CLREvent

   =79b05808 AwareLock::AllocLockCrst : CrstStatic

 

!threads中输出中可以发现0x07f02270是线程70。所以这里存在一个相互等待产生的死锁。

下面我们试着找出AwareLock对应的对象。前面提到提到过CLR使用一个全局数组(g_pSyncTable)来储存SyncBlock

数组中元素的结构如下

Struct SyncTableEntry {

    PTR_SyncBlock    m_SyncBlock;

    Object   *m_Object;

};

 

x mscorwks!g_pSyncTable

79af8354 mscorwks!g_pSyncTable

 

dd 79af8354 L1

79af8354  00152980  (数组地址)

 

dd 0x00152980 L60

00152980  00000000 00140178 0019a50c 00ac27d8

00152990  0019a540 00ac2f60 0019a5a8 00b64c18

001529a0  0019a5dc 00b5edfc 0019a574 00b45474

001529b0  0019a610 00b74a28 0019b0a0 00000001

001529c0  0019a678 00ae54c8 0019a714 00b730e0

001529d0  0019a6e0 00ae7c1c 0019a6ac 00ae7ca4

001529e0  0019a748 00b3fb24 0019b06c 0bb6c3f4

001529f0  0019af34 0000000f 0019b108 0000001d

00152a00  0019b0d4 0000001f 0019b038 00000021

00152a10  0019b004 00000023 0019afd0 00000025

00152a20  0019af9c 00000027 0019af68 00000029

00152a30  0019a84c 0bb5dc28 0019ac90 0bb5ccc4

00152a40  0019abf4 0000002b 0019a9b8 00000031

00152a50  0019ad60 00000033 0019acc4 00000035

00152a60  0019ab8c 00000037 0019aa54 00000039

00152a70  0019ab24 00ac0544 0019ab58 00c39dac

00152a80  0019adfc 00b979c4 0019aabc 00ae5730

00152a90  0019ad2c 00baeefc 0019adc8 00b9810c

00152aa0  0019acf8 00c74b78 0019ac28 00bd3130

00152ab0  0019a7e4 00b651e4 0019aaf0 00b68ba4

00152ac0  0019aa20 00f635c8 0019a8e8 00b92cf8

00152ad0  0019aa88 00b961cc 0019a984 00b5badc

00152ae0  0019a8b4 00b640f4 0019a644 00b93440

00152af0  0019a77c 00baf644 0019ac5c 00ae6d4c

 

AwareLock 0019adfc 0019adc8 对应的对象分别是00b979c400b9810c

使用SOS命令“!DumpObj”查看对象的信息。

在这个例子中输出如下:

!DumpObj 00b979c4

Name: System.Object

MethodTable: 788ed048

EEClass: 788ecfe0

Size: 12(0xc) bytes

 (C:\WINNT\assembly\GAC_32\mscorlib\2.0.3600.0__b77a5c561934e089\mscorlib.dll)

Object

Fields:

None

 

显然没有足够的信息。幸运的是SOS中有一个命令可以帮我们:

!gcroot 00b979c4

ESP:79df6bc:Root:00b976f8->00b979c4(System.Object)

 

!DumpObj 00b976f8

 

最后可以使用“kb”“!clrstack”来定位源代码中的错误。

 

SOS中还有一个非常有用的命令“!DumpStackObjects”。DumpStackObjects遍历寄存器和栈空间并列出有效的托管对象。

在线程7042上使用DumpStackObjects。输出凑巧是:

0:070> !DumpStackObjects

ESP/REG  Object   Name

0a21f334 00b979c4 System.Object (第一项)

。。。

 

0:042> !DumpStackObjects

ESP/REG  Object   Name

0da3f3b4 00b9810c Microsoft.Exchange.Common.IpConnectionPool.OutboundIpConnection (第一项)

。。。

 

顺便说一句,在调试托管异常时可以使用DumpStackObjects迅速找到异常对象。

 

 

 

posted on 2004-10-22 09:18:00 by gangp  评论(4) 阅读(4727)

 
2004年09月08日

在Whidbey上运行如下的两个测试并解释结果:

 

Test 1:

using System;

class Test

{

    static public void Main()

    {

        Console.WriteLine(object.ReferenceEquals(string.Empty, string.Intern(string.Empty)));

    }

}

 

Test 2:

using System;

class Test

{

    static public void Main()

    {

        Console.WriteLine(object.ReferenceEquals(string.Empty, string.Intern(string.Empty)));

        Console.WriteLine(object.ReferenceEquals(string.Empty, string.Intern("")));

    }

}

posted on 2004-09-08 05:42:00 by gangp  评论(7) 阅读(2968)

 

http://blogs.msdn.com/bclteam/archive/2004/09/07/226487.aspx

 

posted on 2004-09-08 05:38:00 by gangp  评论(7) 阅读(3052)

 
2004年08月22日

问题: http://blog.joycode.com/gangp/archive/2004/08/14/30672.aspx

 

Race的根源在于 dataFlag |= tmp; 不是一个Atomic操作。这条语句会转换成三条指令:读取dataFlag, dataFlag | tmp,设置dataFlag

 

下面假设有三个线程同时执行。dataFlag 0CheckSwitch1CheckSwitch2返回true

Thread 1: 调用IsSwitch1On

        if ((dataFlag & SwitchFlags.Switch1Ready) == 0) {   // 条件为True

            SwitchFlags tmp = SwitchFlags.Switch1Ready;    

            if(CheckSwitch1()) tmp |= SwitchFlags.Switch1On;

  // tmp SwitchFlags.Switch1On | SwitchFlags.Switch1Ready;     

            dataFlag |= tmp; // 在读取dataFlag后失去控制权(context switch

            ―――――――――

           

 

Thread 2: 调用IsSwitch2On

        if ((dataFlag & SwitchFlags.Switch2Ready) == 0) { // 条件为True

            SwitchFlags tmp = SwitchFlags.Switch2Ready;

            if(CheckSwitch2()) tmp |= SwitchFlags.Switch2On;

  dataFlag |= tmp;  // dataFlag被设置为Switch2On | Switch2Ready;     

            return (tmp & SwitchFlags.Switch2On) != 0;  //返回True

        }

 

Thread 3: 调用IsSwitch2On

        if ((dataFlag & SwitchFlags.Switch2Ready) == 0) { // 条件为False

           

        }

        ―――――――――

        //在此失去控制权(context switch

        return (dataFlag & SwitchFlags.Switch2On) != 0;

 

Thread 1

        

dataFlag |= tmp;  // dataFlag 的值被设置为Switch1On | Switch1Ready;

       

Thread 3:

        return (dataFlag & SwitchFlags.Switch2On) != 0; //返回False

 

解决的方案有:(1)使用独立的变量存储不同的Switch的值。(2)在修改共享变量时使用Interlocked方法或lock

posted on 2004-08-22 02:55:00 by gangp  评论(8) 阅读(3630)

 
2004年08月14日

今天遇到了一个较有代表性的竟态条件。下面的代码(改编自Net类库中的代码)缓存比较昂贵的计算结果。为了节省空间,所以的信息全部储存在一个变量中:

 

class DataCache{

    bool CheckSwitch1() { // expensive calculation }

    bool CheckSwitch2() { // expensive calculation }

 

[Flags] enum SwitchFlags {

    Switch1Ready = 0x01, Switch2Ready = 0x02, Switch1On = 0x04,  Switch2On = 0x08

}

 

    SwitchFlags dataFlag = 0;

  

    bool IsSwitch1On() {

        if ((dataFlag & SwitchFlags.Switch1Ready) == 0) {

            SwitchFlags tmp = SwitchFlags.Switch1Ready;

            if(CheckSwitch1()) tmp |= SwitchFlags.Switch1On;

            dataFlag |= tmp;

            return (tmp & SwitchFlags.Switch1On) != 0;

        }

        return (dataFlag & SwitchFlags.Switch1On) != 0;

    }

 

    bool IsSwitch2On() {

        if ((dataFlag & SwitchFlags.Switch2Ready) == 0) {

            SwitchFlags tmp = SwitchFlags.Switch2Ready;

            if(CheckSwitch2()) tmp |= SwitchFlags.Switch2On;

            dataFlag |= tmp;

            return (tmp & SwitchFlags.Switch2On) != 0;

        }

        return (dataFlag & SwitchFlags.Switch2On) != 0;

    }

}

请问在什么情况下会出错?

posted on 2004-08-14 12:54:00 by gangp  评论(8) 阅读(3313)

 

在使用Process类来打开未知类型文件时,会得到错误代码为ERROR_NO_ASSOCIATION Win32Exception

设置ProcessStartInfo中的ErrorDialog可以显示“Open With”对话框:

using System.Diagnostics;

 

class Test {

   public static void Main() {

       ProcessStartInfo info = new ProcessStartInfo();

       info.FileName = "t.tt";   

       info.ErrorDialog = true;

       Process.Start(info);

   }

}

posted on 2004-08-14 12:21:00 by gangp  评论(8) 阅读(3150)

 

只有在STA线程上ShellExecute 才能确保工作无误。在Process的实现中,并没有考虑到这个问题,所以使用Process类运行ShellExecute可能会出错。如果你不能保证调用Process.Start的线程的ApartmentState,可以使用如下的代码来避免这个问题:

 

using System;

using System.Threading;

public class Foo {

    public static void OpenUrl()    {

        System.Diagnostics.Process.Start(@"http://www.google.com");

    }

 

    public static void Main() {

        ThreadStart openUrlDelegate = new ThreadStart(Foo.OpenUrl);

        Thread myThread = new Thread(openUrlDelegate);

        myThread.SetApartmentState(ApartmentState.STA);   

        myThread.Start();

        myThread.Join();

    }

}

posted on 2004-08-14 12:13:00 by gangp  评论(11) 阅读(6469)

 
2004年07月20日

使用Collection<基类>来存储继承类是一个常见的模式。在理想的设计中,基类应当定义用户所需要使用的所有接口。这样通过多态性,不同的继承类可以有不同的行为。然而在现实中,我们往往需要使用类型检查或Cast来判断对象的具体类型, 然后根据不同的类型进行不同的操作。

    if( obj.GetType() == typeof(BaseClass)) {

       

}

   

    Derived d (DerivedbaseObj;

   

    Derived d baseObj as Derived;

 

WhidbeyJIT编译器对这两种情况进行了优化。下面是一段C#代码(仅作说明使用):

 

using System;

class Test {

    public static void Main() {

        Console.ReadLine();

        object o = "abcd";

        if(o.GetType() == typeof(string)) {

            string s = (string)o;

            Console.WriteLine(s);

        }

    }

}  

 

 

相应的汇编代码如下:

CLR 1.1:

; code for type check

06f80066 8b350417ac05     mov     esi,[05ac1704] ("abcd")

06f8006c 8bce             mov     ecx,esi

06f8006e 3909             cmp     [ecx],ecx

06f80070 ff15b0c3b779  call dword ptr [mscorlib_79980000+0x1fc3b0 (79b7c3b0)] (System.Object.GetType)

06f80076 b9f8dab779    mov     ecx,0x79b7daf8 (MT: System.String)

06f8007b 89442404      mov     [esp+0x4],eax

06f8007f e8c7152772    call mscorwks!COMClass::GetClassFromHandle (791f164b)

06f80084 8b542404         mov     edx,[esp+0x4]

06f80088 3bd0             cmp     edx,eax

06f8008a 751c             jnz     06f800a8

 

; code for cast

06f8008c 8bd6             mov     edx,esi

06f8008e b9f8dab779       mov     ecx,0x79b7daf8 (MT: System.String)

06f80093 e8840b2772       call    mscorwks!JIT_ChkCastClass (791f0c1c)

 

Whidbey:

; code for type check

0377007e 8b351c20ac01     mov     esi,[01ac201c] ("abcd")

03770084 8bce             mov     ecx,esi

03770086 baf4adba78       mov     edx,0x78baadf4 (MT: System.String)

0377008b e8204b0876       call    mscorwks!JIT_IsObj_Typehandle (797f4bb0)

03770090 85c0             test    eax,eax

03770092 743f             jz      image00400000!Test.Main()+0x63 (037700d3)

  

;code for cast

03770094 85f6             test    esi,esi

03770096 741a             jz      image00400000!Test.Main()+0x42 (037700b2)

03770098 813ef4adba78     cmp     dword ptr [esi],0x78baadf4

0377009e 7502             jnz     image00400000!Test.Main()+0x32 (037700a2)

037700a0 eb0e             jmp     image00400000!Test.Main()+0x40 (037700b0)

037700a2 8bd6             mov     edx,esi

037700a4 b9f4adba78       mov     ecx,0x78baadf4 (MT: System.String)

037700a9 e8a0522976       call    mscorwks!JIT_ChkCastClass (79a0534e)

037700ae 8bf0             mov     esi,eax

037700b0 eb00             jmp     image00400000!Test.Main()+0x42 (037700b2)

 

相对与1.1而言,Whidbey上类型检查的汇编代码简单了许多。对常见的对象而言,JIT_IsObj_Typehandle仅仅是将对象MethodTable(储存在每个对象的开头)和一个固定的值进行比较。所以在Whidbey上类型检查速度提高了很多。

 

WhidbeyCast的汇编代码反而更大。但是在通常情况下速度会有所提高。原因是如果Cast的成功时,JIT_ChkCastClass不会被调用。JIT_ChkCastClass的基本工作是比较对象的MethodTable和指定的MethodTable,在不匹配时获取父类的MethodTable继续进行比较。所以在类的层次(Hierarchy)很深时,JIT_ChkCastClass会较费时间。

 

理论上而言。Whidbey所生成的代码还有改进的余地。因为从上下文可以知道这里的Cast一定会成功。所以Cast的代码完全没有必要。希望在以后的版本中能得到改进。

 

posted on 2004-07-20 10:32:00 by gangp  评论(8) 阅读(3545)

 
2004年07月18日

当系统中有活跃的System.Threading.Timer对象,CLR会建立一个Timer线程。

该线程获得执行权时会查询系统中注册的所有System.Threading.Timer对象并

调用相应的回调函数。

1.01.1中,System.Timers.Timer使用windows提供的Waitable Timer。所以System.Timers.TimerSystem.Threading.Timer的实现不同。

Whidbey System.Timers.Timer使用System.Threading.Timer。区别在于System.Timers.Timer可以用于可视化的插入窗体中。

posted on 2004-07-18 14:27:00 by gangp  评论(16) 阅读(10630)

 
2004年07月17日

函数调用有一定的代价:参数需要保存到堆栈上或寄存中(在可能的情况下JIT使用ecxedx来传递前俩个参数),执行函数的prologepilog也需要一定的时间。Inlining可以避免函数调用的代价,然而CLRJIT inliner较为保守。下面是一些最重要的规则:

l           IL代码大于32字节的函数不被inline

l           虚拟函数(virtual method)调用或接口函数(interface method)调用不被inline

l           使用复杂的程序流控制的函数不被inline。复杂的程序流控制指if/then/else之外的流控制语句。

l           包含异常处理快的方法和产生异常的方法不被inline。可以将throw语句移到一个独立的函数中来避免这个问题。下面是一个例子:

        public T this[int index] {

            get {

                // Fllowing trick can reduce the range check by one

                if ((uint) index >= (uint)_size) { ThrowHelper.ThrowIndexOutOfRangeException();}

                return _items[index];

            }

        }

l           包含值类型参数的方法不被inline

注意不要为了inline一个方法而牺牲程序的正确性。同时要注意inline较大的函数可能导致工作集的增长。

如果你读过如下的文章:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/highperfmanagedapps.asp

你会注意到我基本上是翻译了该文章的一部分。不过注意有分歧的地方可以假设我的信息更准确。:)

posted on 2004-07-17 09:51:00 by gangp  评论(10) 阅读(5024)

 

 接受非负整数为参数的方法通常需要做范围检查。下面是一个例子(ArrayList.RemoveAt):

        public virtual void RemoveAt(int index) {

            if (index < 0 || index >= _size) {

throw new ArgumentOutOfRangeException("index");

            }

                    

        }

实际上这里并不需要两次检查。如下的代码可以起到相同的作用:

        public virtual void RemoveAt(int index) {

            if ((uint)index >= _size) {

throw new ArgumentOutOfRangeException("index");

            }

                    

        }

当然不到必要的时候不要使用这个Trick因为这会降低程序的可读性。

 

posted on 2004-07-17 08:20:00 by gangp  评论(11) 阅读(3212)

 
2004年07月08日

    public interface ICollection : IEnumerable {

           void CopyTo(Array array, int index);

           int Count { get; }

           Object SyncRoot { get; }

           bool IsSynchronized     { get; }

}

 

    public interface ICollection : IEnumerable {

        int Count { get; }

        bool IsReadOnly { get; }

        void Add(T item);

        void Clear();

        bool Contains(T item);

        void CopyTo(T[] array, int arrayIndex);                

        bool Remove(T item);

}

ICollection相比,泛型ICollection接口中增加了许多方法。ICollection中的方法太少,所以实际上没有什么用处。ICollecion使定义新的Collection(比如LinkedListTree可以实现ICollection<T>)更加方便。

 

Synchronization有关的方法被去掉了,原因是使用Framework提供的Wrapper并不能保证正确的Synchronization. 下面是一个例子:

         Hashtable hashtable = Hashtable.Synchronized(new Hashtable());  

      if(!hashtable.Contains(key)) {

              hashtable.Add(key, value);

}

Add方法还是有可能产生异常。用户有更多的信息来决定什么时候需要使用lock以及lock的范围。

 

例外Remove方法返回bool,用于指明指定的值是否存在于Collection中。

posted on 2004-07-08 09:49:00 by gangp  评论(8) 阅读(4524)

 
2004年04月28日
【第1页/共2页,30条】
首页
前页
1

Powered by: Joycode.MVC引擎 0.5.2.0