临界区死锁和死循环

本文详细介绍了Windows中使用CRITICAL_SECTION实现线程同步的临界区机制,包括其内部结构和字段的含义,并通过经典死锁案例展示了线程等待临界区的情况。此外,还提供了查找死循环的调试步骤,帮助开发者理解和解决多线程同步问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.临界区

目录

1.临界区

2. 经典死锁案例:

3.查找死循环案例


使用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 及源码请查看软件调试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值