一直不明白为什么明明已经在主线程已经进入了关键区
(EnterCriticalSection),但是为什么主线程种执行等待的时候没有阻塞,查了不少资料后明白了关键段【线程拥有权】的概念。
首先先记录以下关键段的概念,帮助自己回顾查看。
关键段用于线程的互斥,类似于互斥锁Mutex(那他们两者之间有什么区别呢?我所了解到的是互斥锁支持【遗弃】问题的处理,所谓遗弃问题,说直白点就是加完互斥锁后,当前线程因为某种原因意外结束退出了,这时候如果其它线程用关键段进行互斥,那么其它线程都将一直等待,进入死锁;二互斥锁后会将当前线程的互斥锁释放,其它线程可以通过线程句柄的状态获取意外退出的状态作相应处理,这在互斥条件下是很重要的!!)
关键段CRITICAL_SECTION一共就四个函数,使用很是方便。下面是这四个函数的原型和使用说明。
函数功能:初始化
函数原型:
void InitializeCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
函数说明:定义关键段变量后必须先初始化。
函数功能:销毁
函数原型:
void DeleteCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
函数说明:用完之后记得销毁。
函数功能:进入关键区域
函数原型:
void EnterCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
函数说明:系统保证各线程互斥的进入关键区域。
函数功能:离开关关键区域
函数原型:
void LeaveCriticalSection(LPCRITICAL_SECTIONlpCriticalSection);
以上是关键段的相关基本介绍,下面直接上我测试的代码来测试主线程关键段可以多次进入的情况:
#include <windows.h>
#include <process.h>
CRITICAL_SECTION g_csThreadCode; //定义关键段句柄
unsigned int __stdcall Fun(void *pPM) //子线程
const unsigned int THREAD_NUM = 10; //线程数量
HANDLE handle[THREAD_NUM]; //线程句柄数组
unsigned int __stdcall Fun(void *pPM) //子线程
{
int nThreadNum = *(int *)pPM; //子线程获取参数
EnterCriticalSection(&g_csThreadCode);
g_nNum++; //处理全局资源
Sleep(1000);//some work should to do
printf("线程句柄%d,获取次数:%d ,当前等待线程数:%d \n",
g_csThreadCode.OwningThread, g_csThreadCode.RecursionCount,
g_csThreadCode.LockCount);
LeaveCriticalSection(&g_csThreadCode);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
InitializeCriticalSection(&g_csThreadCode); //初始化关键段
printf("当前线程句柄%d\n\n", GetCurrentThreadId());
int i=0;
while (i < THREAD_NUM){
EnterCriticalSection(&g_csThreadCode);
handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
printf("线程句柄%d,获取次数:%d ,当前等待线程数:%d \n",
g_csThreadCode.OwningThread,
g_csThreadCode.RecursionCount,
g_csThreadCode.LockCount);
Sleep(1000);
// LeaveCriticalSection(&g_csThreadCode);
//【注意此处】:分两种情况测试,不执行LeaveCriticalSection和执行LeaveCriticalSection,观察线程参数记录参数的状态,下面会介绍!
i++;
//同一线程不会锁住,只会增加计数。
}
WaitForMultipleObjects(THREAD_NUM , handle, TRUE, INFINITE);
DeleteCriticalSection(&g_csThreadCode);
i=0;
while(i<10){
CloseHandle(handle[i]);
i++;
}
system("pause");
return 0;
}
附上运行结果:
好了,问题来了,按理来说,主线程执行完EnterCriticalSection加锁,然后我把释放锁LeaveCriticalSection注释了,这个时候发现下次循环的时候,线程没有阻塞等待,而是又一次执行下去,这不符合互斥的初衷啊。这就是我之前的困惑,然后看了CRITICAL_SECTION的定义:
它在WinBase.h中被定义成RTL_CRITICAL_SECTION。而RTL_CRITICAL_SECTION在WinNT.h中声明,它其实是个结构体:
typedef struct _RTL_CRITICAL_SECTION {
PRTL_CRITICAL_SECTION_DEBUGDebugInfo;
LONGLockCount;
LONGRecursionCount;
HANDLEOwningThread; // from the thread's ClientId->UniqueThread
HANDLELockSemaphore;
DWORDSpinCount;
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
各个参数的解释如下:
第一个参数:PRTL_CRITICAL_SECTION_DEBUGDebugInfo;
调试用的。
第二个参数:LONGLockCount;
初始化为-1,n表示有n个线程在等待。
第三个参数:LONGRecursionCount;
表示该关键段的拥有线程对此资源获得关键段次数,初为0。
第四个参数:HANDLEOwningThread;
即拥有该关键段的线程句柄
第五个参数:HANDLELockSemaphore;
实际上是一个自复位事件。
第六个参数:DWORDSpinCount;
旋转锁的设置,单CPU下忽略
可见,主线程的线程ID和第4个参数HANDLEOwningThread返回的句柄ID是一致 的,即当前锁是属于主线程的,主线程对这个关键段拥有所有权,这也就可以理解为什么主线程可以不释放就可以再次进入关键段区了。 打个比方,在酒店开房后,你拿了房卡你就是主人,拥有房间的进出权,在退房之前,你可以多次进出房间;但是假如你的朋友要进去,因为他没有房间的所有权,所以只能在你把房卡交给他的时候他能用一次,用完给你之后就进不去了。
所以,当主线程多次进入关键段区的时候,第3个参数LONGRecursionCount会记录进入的次数,会不断累计,这也就想明白了。
如果把主线程的LeaveCriticalSection注释去掉,那么当前获取次数就一直为1,如下图:
当前获取次数一直是1,因为进入和离开是成对使用的。
以上就是我的发现,但是【当前等待线程数】的值还是没明白,命名启动了10个线程,最多也就是到-10,为什么最高有-38?
太晚了先写到这里,对了,同时我也测试了互斥锁,也是这种情况,如果想在主线程种也会阻塞,可以用事件Event来实现。