posted on 2004-11-19 11:17:00 by gangp 评论(10) 阅读(3891)
New Article Posted at:
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的拥有权,CLR将SpinLock升级为一个AwareLock。AwareLock类似与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_MonContention的EBP值,所以我们可以得到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使用一个全局数组来储存SyncBlock,m_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 对应的对象分别是00b979c4和00b9810c。
使用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遍历寄存器和栈空间并列出有效的托管对象。
在线程70和42上使用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)
在Whidbey上运行如下的两个测试并解释结果:
Test 1:
using System;
class Test
{
static public void
{
Console.WriteLine(object.ReferenceEquals(string.Empty, string.Intern(string.Empty)));
}
}
Test 2:
using System;
class Test
{
static public void
{
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)
posted on 2004-09-08 05:38:00 by gangp 评论(7) 阅读(3052)
问题: http://blog.joycode.com/gangp/archive/2004/08/14/30672.aspx
Race的根源在于 dataFlag |= tmp; 不是一个Atomic操作。这条语句会转换成三条指令:读取dataFlag, dataFlag | tmp,设置dataFlag。
下面假设有三个线程同时执行。dataFlag 为0,CheckSwitch1和CheckSwitch2返回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)
今天遇到了一个较有代表性的竟态条件。下面的代码(改编自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
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
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)
使用Collection<基类>来存储继承类是一个常见的模式。在理想的设计中,基类应当定义用户所需要使用的所有接口。这样通过多态性,不同的继承类可以有不同的行为。然而在现实中,我们往往需要使用类型检查或Cast来判断对象的具体类型, 然后根据不同的类型进行不同的操作。
if( obj.GetType() == typeof(BaseClass)) {
…
}
或
Derived d = (Derived)baseObj;
或
Derived d =baseObj as Derived;
Whidbey中JIT编译器对这两种情况进行了优化。下面是一段C#代码(仅作说明使用):
using System;
class Test {
public static void
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上类型检查速度提高了很多。
Whidbey上Cast的汇编代码反而更大。但是在通常情况下速度会有所提高。原因是如果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)
当系统中有活跃的System.Threading.Timer对象,CLR会建立一个Timer线程。
该线程获得执行权时会查询系统中注册的所有System.Threading.Timer对象并
调用相应的回调函数。
在1.0和1.1中,System.Timers.Timer使用windows提供的Waitable Timer。所以System.Timers.Timer与System.Threading.Timer的实现不同。
在Whidbey System.Timers.Timer使用System.Threading.Timer。区别在于System.Timers.Timer可以用于可视化的插入窗体中。
posted on 2004-07-18 14:27:00 by gangp 评论(16) 阅读(10630)
函数调用有一定的代价:参数需要保存到堆栈上或寄存中(在可能的情况下JIT使用ecx和edx来传递前俩个参数),执行函数的prolog和epilog也需要一定的时间。Inlining可以避免函数调用的代价,然而CLR的JIT 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较大的函数可能导致工作集的增长。
如果你读过如下的文章:
你会注意到我基本上是翻译了该文章的一部分。不过注意有分歧的地方可以假设我的信息更准确。:)
posted on 2004-07-17 09:51:00 by gangp 评论(10) 阅读(5024)
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)
public interface ICollection : IEnumerable {
void CopyTo(Array array, int index);
int Count { get; }
Object SyncRoot { get; }
bool IsSynchronized { get; }
}
public interface ICollection
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
与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)
posted on 2004-04-28 09:32:00 by gangp 评论(7) 阅读(3232)