1.临界区
目录
使用CRITICAL_SECTION 结构来保证不会有多个线程重入被保护的代码段
实现在用户态的同步机制,相对内核对象来说,开销更小
适用于同步同一进程内的多个线程
如:
CRITICAL_SECTION g_cs;
EnterCriticalSection(&g_cs);
//以原子方式访问共享资源
..... //do something
LeaveCriticalSection(&g_cs);
RTL_CRITICAL_SECTION 结构如下:
struct RTL_CRITICAL_SECTION
{
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread;
HANDLE LockSemaphore;
ULONG_PTR SpinCount;
};
LockCount字段,初始值为-1,被线程拥有后,大于等于0,反映等待和已经进入关键区的线程数RecursionCount字段,此字段包含所有者线程已经获得该临界区的次数。如果该数值为零,下一个尝试获取该临界区的线程将会成功。
OwningThread字段,拥有线程(已经进入临界区)的句柄。
LockSemaphore字段,唤醒等待的线程在初始化临界区结构时,系统会分配一个RTL_CRITICAL_SECTION_DEBUG结构,每个临界区的调试结构依靠ProcessLocksList字段相互联系在一起。
SpinCount 仅用于多处理器系统。MSDN文档对此字段进行如下说明:“在多处理器系统中,如果该临界区不可用,调用线程将在对与该临界区相关的信号执行等待操作之前,旋转 dwSpinCount 次。如果该临界区在旋转操作期间变为可用,该调用线程就避免了等待操作。”旋转计数可以在多处理器计算机上提供更佳性能,其原因在于在一个循环中旋转通常要快于进入内核模式等待状态。此字段默认值为零,但可以用 InitializeCriticalSectionAndSpinCount API 将其设置为一个不同值。
临界区的调试支持,RTL_CRITICAL_SECTION_DEBUG结构如下:
struct _RTL_CRITICAL_SECTION_DEBUG
{
WORD Type;
WORD CreatorBackTraceIndex;
RTL_CRITICAL_SECTION *CriticalSection;
LIST_ENTRY ProcessLocksList;
DWORD EntryCount;
DWORD ContentionCount;
DWORD Spare[ 2 ];
}
这一结构由 InitializeCriticalSection 分配和初始化。它既可以由 NTDLL内的预分配数组分配,也可以由进程堆分配。RTL_CRITICAL_SECTION的这一伴随结构包含一组匹配字段,具有迥然不同的角色:有两个难以理解,随后两个提供了理解这一临界区链结构的关键,两个是重复设置的,最后两个未使用。
下面是对 RTL_CRITICAL_SECTION 字段的说明。
Type 此字段未使用,被初始化为数值 0。
CreatorBackTraceIndex 此字段仅用于诊断情形中。在注册表项
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File ExecutionOptions\YourProgram 之下是 keyfield、GlobalFlag 和 StackTraceDatabaseSizeInMb值。注意,只有在运行稍后说明的 Gflags 命令时才会显示这些值。这些注册表值的设置正确时,CreatorBackTraceIndex
字段将由堆栈跟踪中所用的一个索引值填充。在 MSDN 中搜索 GFlags 文档中的短语“create user mode stack trace database”和“enlarging the user-mode stack trace database”,可以找到有关这一内容的更多信息。
CriticalSection 指向与此结构相关的 RTL_CRITICAL_SECTION。
图1 说明该基础结构以及 RTL_CRITICAL_SECTION、RTL_CRITICAL_SECTION_DEBUG 和事件链中其他参与者之间的关系。
图 1 临界区的关系
ProcessLocksList LIST_ENTRY 是用于表示双向链表中节点的标准 Windows数据结构。RTL_CRITICAL_SECTION_DEBUG 包含了链表的一部分,允许向前和向后遍历该临界区。
EntryCount/ContentionCount 这些字段在相同的时间、出于相同的原因被递增。这是那些因为不能马上获得临界区而进入等待状态的线程的数目。与 LockCount 和 RecursionCount字段不同,这些字段永远都不会递减。
Spares这两个字段未使用,甚至未被初始化(尽管在删除临界区结构时将这些字段进行了清零)。
即使 RTL_CRITICAL_SECTION_DEBUG中包含多个字段,它也是常规临界区结构的必要成分。事实上,如果系统恰巧不能由进程堆中获得这一结构的存储区,InitializeCriticalSection
将返回为 STATUS_NO_MEMORY 的 LastError 结果,然后返回处于不完整状态的临界区结构。
2. 经典死锁案例:
启动windbg 附加MulThrds.exe
·1.~* 查看所有线程
2.~* k 查看所有线程堆栈
发现有几个线程都在等待进入同一个临界区
3.切换其中某一个线程,查看堆栈,如切到换10号线程
4.查看临界区 !cs 0040b32c
5.查看死锁的临界区,刚好也是0040b32c
拥有的线程是0x00002e14,查看所有线程,发现0x00002e14 已经不在了。
3.查找死循环案例
启动windbg 附加MulThrds.exe
1.查看线程运行时间
~*e .ttime;.echo*****
2.输出线程tid
~*e .echo **********;? @$tid; .ttime;
3.切换线程
~~[4040]s
查看堆栈信息:
参考 : 软件调试 ----张银奎
MulThrds.exe 及源码请查看软件调试