.NET线程同步问题的调试方法
问题介绍
.NET中线程同步经常用到以下方式,
lock statement
AutoResetEvent/MannualResetEvent
Mutex
Semaphore
ReaderWriterLock
Interlocked
线程同步经常遇到的问题有以下几种,
等待 - 等待锁释放
死锁 - 请求锁资源顺序不一致而造成互锁
脏读 - 线程访问资源缺少同步机制保护
死锁也是等待,只是死锁的等待程序无法自解。
脏读一般用在数据库系统中的概念,即多个线程访问同一个资源过程中读与写同时进行。例如在循环遍历某个数组的时候抛出了异常报告正在遍历的数组被更改,Collection was modified; enumeration operation may not execute.
这类问题造成的原因主要是由于在写的过程中缺少排他锁,相应的解决方案可以考虑减少资源的可见度,把资源的访问控制在有限的几个特定的方法中,然后在这些特定的方法上面相应的读写处加锁来避免脏读发生。
调试方法
lock statement
调试等待问题首先还是要先确定线程对象,弄清楚谁在等谁?下面是一个使用lock statement (Monitor class)的简单线程等待的例子,3号线程在Sleep,4号线程在等待。根据调用栈我们可以知道四号线程是在调用Monitor.Enter()方法。
3 Id: 27fc.7f0 Suspend: 1 Teb: 000007f5`ffae6000 Unfrozen
Child-SP RetAddr Call Site
00000000`1b9fea28 000007f8`0e6b11f2 ntdll!NtDelayExecution+0xa
00000000`1b9fea30 000007ff`f803369d KERNELBASE!SleepEx+0xaa
00000000`1b9fead0 000007ff`f7c2e7fd mscorwks!EESleepEx+0x2d
00000000`1b9feb50 000007ff`f81f0769 mscorwks!Thread::UserSleep+0x71
00000000`1b9febb0 000007ff`98500486 mscorwks!ThreadNative::Sleep+0xf9
00000000`1b9fed60 000007ff`f30d2bdb Synchronization!Synchronization.Program.ThreadProc()+0x106
00000000`1b9fee00 000007ff`f316a9bd mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)+0x9b
00000000`1b9fee50 000007ff`f7dbd552 mscorlib_ni!System.Threading.ThreadHelper.ThreadStart()+0x4d
00000000`1b9feea0 000007ff`f7cfa283 mscorwks!CallDescrWorker+0x82
00000000`1b9feef0 000007ff`f819c7af mscorwks!CallDescrWorkerWithHandler+0xd3
00000000`1b9fef90 000007ff`f7be4415 mscorwks!MethodDesc::CallDescr+0x2af
00000000`1b9ff1d0 000007ff`f7d3cda4 mscorwks!ThreadNative::KickOffThread_Worker+0x191
00000000`1b9ff4f0 000007ff`f7d23721 mscorwks!ManagedThreadBase_DispatchInner+0x2c
00000000`1b9ff540 000007ff`f7c1fc59 mscorwks!ManagedThreadBase_DispatchMiddle+0x9d
00000000`1b9ff610 000007ff`f7d7c949 mscorwks!ManagedThreadBase_DispatchOuter+0x31
00000000`1b9ff650 000007ff`f7d472d3 mscorwks!ManagedThreadBase_FullTransitionWithAD+0x35
00000000`1b9ff6b0 000007ff`f7d3f3a4 mscorwks!ThreadNative::KickOffThread+0xd3
00000000`1b9ff790 000007f8`0f16167e mscorwks!Thread::intermediateThreadProc+0x78
00000000`1b9ff8e0 000007f8`116d3501 KERNEL32!BaseThreadInitThunk+0x1a
00000000`1b9ff910 00000000`00000000 ntdll!RtlUserThreadStart+0x21
4 Id: 27fc.2634 Suspend: 1 Teb: 000007f5`ffae4000 Unfrozen
Child-SP RetAddr Call Site
00000000`1bafe5c8 000007f8`0e6b12c6 ntdll!ZwWaitForMultipleObjects+0xa
00000000`1bafe5d0 000007ff`f7c1e7f9 KERNELBASE!WaitForMultipleObjectsEx+0xe5
00000000`1bafe8b0 000007ff`f7c231e1 mscorwks!WaitForMultipleObjectsEx_SO_TOLERANT+0xc1
00000000`1bafe950 000007ff`f7d203d5 mscorwks!Thread::DoAppropriateAptStateWait+0x41
00000000`1bafe9b0 000007ff`f7c3e94c mscorwks!Thread::DoAppropriateWaitWorker+0x191
00000000`1bafeab0 000007ff`f7c7d16a mscorwks!Thread::DoAppropriateWait+0x5c
00000000`1bafeb20 000007ff`f7c00fd1 mscorwks!CLREvent::WaitEx+0xbe
00000000`1bafebd0 000007ff`f7d4e002 mscorwks!AwareLock::EnterEpilog+0xc9
00000000`1bafeca0 000007ff`f815a815 mscorwks!AwareLock::Enter+0x72
00000000`1bafecd0 000007ff`98500427 mscorwks!JIT_MonEnterWorker_Portable+0xf5
00000000`1bafeea0 000007ff`f30d2bdb Synchronization!Synchronization.Program.ThreadProc()+0xa7
00000000`1bafef40 000007ff`f316a9bd mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)+0x9b
00000000`1bafef90 000007ff`f7dbd552 mscorlib_ni!System.Threading.ThreadHelper.ThreadStart()+0x4d
00000000`1bafefe0 000007ff`f7cfa283 mscorwks!CallDescrWorker+0x82
00000000`1baff030 000007ff`f819c7af mscorwks!CallDescrWorkerWithHandler+0xd3
00000000`1baff0d0 000007ff`f7be4415 mscorwks!MethodDesc::CallDescr+0x2af
00000000`1baff310 000007ff`f7d3cda4 mscorwks!ThreadNative::KickOffThread_Worker+0x191
00000000`1baff630 000007ff`f7d23721 mscorwks!ManagedThreadBase_DispatchInner+0x2c
00000000`1baff680 000007ff`f7c1fc59 mscorwks!ManagedThreadBase_DispatchMiddle+0x9d
00000000`1baff750 000007ff`f7d7c949 mscorwks!ManagedThreadBase_DispatchOuter+0x31
00000000`1baff790 000007ff`f7d472d3 mscorwks!ManagedThreadBase_FullTransitionWithAD+0x35
00000000`1baff7f0 000007ff`f7d3f3a4 mscorwks!ThreadNative::KickOffThread+0xd3
00000000`1baff8d0 000007f8`0f16167e mscorwks!Thread::intermediateThreadProc+0x78
00000000`1baffaa0 000007f8`116d3501 KERNEL32!BaseThreadInitThunk+0x1a
00000000`1baffad0 00000000`00000000 ntdll!RtlUserThreadStart+0x21
由于Monitor.Enter涉及到一个同步对象,我们可以尝试找一下这个同步对象,在本地变量里面找到一个object,
0:004> !clrstack -a
OS Thread Id: 0x2634 (4)
Child-SP RetAddr Call Site
000000001bafeea0 000007fff30d2bdb Synchronization.Program.ThreadProc()
PARAMETERS:
this = 0x0000000003023bf0
LOCALS:
0x000000001bafeec8 = 0x0000000003023c08
000000001bafef40 000007fff316a9bd System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
PARAMETERS:
executionContext = <no data>
callback = <no data>
state = <no data>
LOCALS:
<no data>
0:004> !do 0x0000000003023c08
Name: System.Object
MethodTable: 000007fff3217510
EEClass: 000007fff2de2200
Size: 24(0x18) bytes
(C:\windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
Object
Fields:
None
注意object地址-4为对象同步块地址。
0:004> dd 0x0000000003023c08-4 l1
00000000`03023c04 08000005
根据rotor中对于syncblk.h的定义,
[cpp] view plaincopy
DWORD GetHeaderSyncBlockIndex()
{
LEAF_CONTRACT;
// pull the value out before checking it to avoid race condition
DWORD value = m_SyncBlockValue;
if ((value & (BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE)) != BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX)
return 0;
return value & MASK_SYNCBLOCKINDEX;
}
#define BIT_SBLK_IS_HASHCODE
补充:Web开发 , ASP.Net ,