CCriticalSection是对关键段CRITICAL_SECTION的封装。

关键段(critival section)是一小段代码,他在执行之前需要独占对一些共享资源的访问权。这种方式可以让多行代码以“原子方式”来对资源进行操控。这里的“原子方式”,指的是代码知道除了当前线程之外没有其他任何线程会同时访问该资源。当然,系统仍然可以暂停当前线程去调度其他线程。但是,在当前线程离开关键段之前,系统是不会去调度任何想要访问同一资源的其他线程的。

例如:如果两个线程同时访问一个链表,一个线程可能会在另一个线程在链表中搜寻一个元素的同时向链表中添加一个元素,将导致搜索结果不正确;还有可能两个线程同时向链表中添加元素,这种情况会变的更加混乱;甚至一个线程搜索的时候,另一个线程删除了链表节点,将直接导致程序崩溃。

解决这个问题,我们可以现在代码中定义一个CRITICAL_SECTION数据结构m_sect,然后把任何需要访问共享资源的代码放在EnterCriticalSection和LeaveCriticalSection之间。


  1. EnterCriticalSection(&m_sect);  

  2. // 共享资源的代码段....


  3. LeaveCriticalSection(&m_sect);  

EnterCriticalSection(&m_sect);
// 共享资源的代码段....

LeaveCriticalSection(&m_sect);


一个 CRITICAL_SECTION结构就像是飞机上的一个卫生间,而马桶则是我们想要保护的资源(用EnterCriticalSection和LeaveCriticalSection组成的围墙包围住“马桶”)。由于卫生间很小,因此在同一时刻只允许一个人在卫生间内使用马桶(在同一时刻只允许一个线程在关键段中使用被保护资源)。

如果有多个总是应该在一起使用的资源,那么我们可以把他们放在同一个“卫生间”中:只需要创建一个CRITICAL_SECTION结构来保护所有这些资源。

关于关键段,需要掌握以下几点:

1、任何要访问共享资源的代码,都必须包含在EnterCriticalSection和LeaveCriticalSection之间。如果忘了哪怕是一个地方,共享资源就有可能被破坏。忘记调用EnterCriticalSection和LeaveCriticalSection,就好像是未经许可就强制进入卫生间一样,线程强行进入并对资源进行操控。只要有一个线程有这种粗暴的行为,资源就会被破坏。

2、关键段CRITICAL_SECTION是个未公开的结构,因为Microsoft认为开发人员不需要理解这个结构的细节。对我们来说,不需要知道这个结构中的成员变量,我们绝对不应该在编写代码的时候用到他的成员。

3、为了对CRITICAL_SECTION结构进行操控,我们必须调用Windows API函数,传入结构的地址。(注意是地址!)也就是说,如果该CRITICAL_SECTION结构生命周期没有结束,那么可以将该结构地址通过自己喜欢的任何方式传给任何线程。

4、在任何线程试图访问被保护的资源之前,必须对CRITICAL_SECTION结构的内部成员进程初始化。我们不知道内部成员,但可以调用Windows函数实现:VOID WINAPI InitializeCriticalSection(__out LPCRITICAL_SECTION lpCriticalSection);

5、当线程不再需要访问共享资源的时候,应调用下面的函数来清理该结构:VOID WINAPI DeleteCriticalSection(__inout LPCRITICAL_SECTION lpCriticalSection);

6、其实CRITICAL_SECTION并不知道什么是共享资源,也不会智能保护共享资源。其根本是,同一个时刻如果有多个线程调用EnterCriticalSection的时候,只有一个线程返回,其余线程则暂停执行,等待前面线程调用LeaveCriticalSection之后再执行。

7、可以看出,进入关键段是没有超时设定的,好像永远不会超时。实际上,对EnterCriticalSection的调用也会超时并引发异常。超时的时间长度由下面这个注册表子项中包含的CriticalSectionTimeout值决定:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager

这个值以秒为单位,他的默认值为2592000秒,大约30天。

8、同一个线程可以随便进入用一个关键段N次,也就是说同一个线程调用EnterCriticalSection无论几次都会返回。不同线程是否能够进入关键段,要看EnterCriticalSection的参数(CRITICAL_SECTION结构的地址)之前是否有线程进入过。要记住:飞机上的卫生间有多个,你可以随便进入无人的卫生间,不能进入有人的卫生间。

弄明白了CRITICAL_SECTION之后,使用CCriticalSection非常方便,如虎添翼。看代码:

//头文件

  1. class CCriticalSection : public CSyncObjet  

  2. {...  

  3. public:  

  4.    CRITICAL_SECTION m_sect;  

  5. public:  

  6. BOOL Unlock();  

  7. BOOL Lock();  

  8. BOOL Lock(DWORD dwTimeout);  

  9. ...  

  10. }  

class CCriticalSection : public CSyncObjet
{...
public:
	CRITICAL_SECTION m_sect;
public:
	BOOL Unlock();
	BOOL Lock();
	BOOL Lock(DWORD dwTimeout);
...
}


// 构造函数

  1. CCriticalSection::CCriticalSection() : CSyncObject(NULL)  

  2. {        

  3. HRESULT hr = S_OK;  

  4. if (!InitializeCriticalSectionAndSpinCount(&m_sect, 0))//可以理解为InitializeCriticalSection,为了效率,加了一个旋转锁。

  5.    {  

  6.        hr =  HRESULT_FROM_WIN32(GetLastError());  

  7.    }  


  8. if (FAILED(hr))  

  9.    {  

  10.        AtlThrow(hr);  

  11.    }        

  12. }  

CCriticalSection::CCriticalSection() : CSyncObject(NULL)
{ 		
	HRESULT hr = S_OK;
	if (!InitializeCriticalSectionAndSpinCount(&m_sect, 0))//可以理解为InitializeCriticalSection,为了效率,加了一个旋转锁。
	{
		hr =  HRESULT_FROM_WIN32(GetLastError());
	}
	
	if (FAILED(hr))
	{
		AtlThrow(hr);
	}		
}


//进入关键段

  1. BOOL CCriticalSection::Lock()  

  2. {    

  3.    ::EnterCriticalSection(&m_sect);  


  4. return TRUE;  

  5. }  

BOOL CCriticalSection::Lock()
{	
	::EnterCriticalSection(&m_sect); 

	return TRUE; 
}



// 离开关键段

  1. BOOL CCriticalSection::Unlock()  

  2. {  

  3.    ::LeaveCriticalSection(&m_sect);  


  4. return TRUE;  

  5. }  

BOOL CCriticalSection::Unlock()
{ 
	::LeaveCriticalSection(&m_sect); 
	
	return TRUE; 
}


// 析构

  1. CCriticalSection::~CCriticalSection()  

  2. {  

  3.    ::DeleteCriticalSection(&m_sect);  

  4. }