嵌套临界区是否有效?

20

比如说,这样做是否有效?

CRITICAL_SECTION cs;

::InitializeCriticalSection( &cs );

::EnterCriticalSection( &cs );      // First level
::EnterCriticalSection( &cs );        // Second level

/* do some stuff */

::LeaveCriticalSection( &cs );        // Second level
::LeaveCriticalSection( &cs );      // First level

::DeleteCriticalSection( &cs );

显然,我从不会有意这样做,但如果由于函数调用导致这种情况发生呢?例如,"第一层"被调用以为复杂(例如搜索)算法锁定对象,"第二层"则在该对象的访问器函数中被调用。

3个回答

38

在已经进入关键部分时再次进入同一关键部分是有效的。根据文档

当线程拥有关键部分所有权后,它可以多次调用EnterCriticalSection或TryEnterCriticalSection而不会阻塞其执行。这可以防止线程在等待其已经拥有的关键部分时出现死锁。每次EnterCriticalSection和TryEnterCriticalSection成功时,线程都会进入关键部分。线程必须为每次进入关键部分调用LeaveCriticalSection。


同样适用于所有其他类型的同步对象 - 互斥锁、信号量等。一旦线程获得了锁,它可以无阻塞地多次重新进入锁。只需确保释放锁的次数与进入锁的次数相同,以便为其他线程正确地释放。 - Remy Lebeau
2
@Remy:不,它并不适用于所有其他类型的同步对象。你当然可以拥有非可重入互斥量。 - R. Martinho Fernandes
标准互斥锁是可重入的。引用MSDN的话说:“拥有互斥锁的线程可以在重复的等待函数调用中指定相同的互斥锁,而不会阻塞其执行。通常情况下,您不会重复等待同一个互斥锁,但这种机制可以防止线程在等待它已经拥有的互斥锁时出现死锁。但是,为了释放其所有权,线程必须为每次互斥锁满足等待条件都调用ReleaseMutex一次。” - Remy Lebeau
@Remy:它特别不适用于信号量,因为您不能随意获取信号量。引用MSDN的话:“重复等待同一信号量对象的线程每次完成等待操作时都会减少信号量的计数;当计数达到零时,线程将被阻塞。” - MSalters

12

根据文档

线程获取临界区域的所有权后,可以多次调用EnterCriticalSection或TryEnterCriticalSection而不阻塞其执行。这可以防止线程在等待已经拥有的临界区域时出现死锁。每次EnterCriticalSection和TryEnterCriticalSection成功时,线程都会进入临界区域。线程必须对每次进入临界区域调用LeaveCriticalSection。


是的,但是...好吧,我认为这是平局。 - Cheeso
谢谢!现在点赞很便宜。顺便说一下,我为Martinho的胜利投了票。PS:我本来想只回答“是”的,但觉得引用来源更好。 - Cheeso

2
为了验证另外两个帖子。WinDbg中的关键部分快速查看显示,关键部分维护一个整数变量来保存递归计数。
0:001> dt RTL_CRITICAL_SECTION
+0x000 DebugInfo : Ptr32 _RTL_CRITICAL_SECTION_DEBUG
+0x004 LockCount : Int4B
+0x008 RecursionCount : Int4B
+0x00c OwningThread : Ptr32 Void
+0x010 LockSemaphore : Ptr32 Void
+0x014 SpinCount : Uint4B 

递归计数 - 一个线程可以多次获取临界区。该字段表示同一线程获取临界区的次数。默认情况下,该字段的值为0,表示没有任何线程拥有临界区。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接