Critical_section结构

临界区是一种防止多个线程同时执行一个特定代码节的机制,这一主题并没有引起太多关注,因而人们未能对其深刻理解。在需要跟踪代码中的多线程处理的性能时,对

Windows 中临界区的深刻理解非常有用。

临界区:简述


如果您非常熟悉临界区,并可以不假思索地进行应用,那就可以略过本节。否则,请向下阅读,以对这些内容进行快速回顾。如果您不熟悉这些基础内容,则本节之后的内容就没有太大意义。


临界区是一种轻量级机制,在某一时间内只允许一个线程执行某个给定代码段。通常在修改全局数据(如集合类)时会使用临界区。事件、多用户终端执行程序和信号量也用于多线程同步,但临界区与它们不同,它并不总是执行向内核模式的控制转换,这一转换成本昂贵。稍后将会看到,要获得一个未占用临界区,事实上只需要对内存做出很少的修改,其速度非常快。只有在尝试获得已占用临界区时,它才会跳至内核模式。这一轻量级特性的缺点在于临界区只能用于对同一进程内的线程进行同步。


临界区由 WINNT.H 中所定义的 RTL_CRITICAL_SECTION 结构表示。因为您的 C++ 代码通常声明一个
CRITICAL_SECTION 类型的变量,所以您可能对此并不了解。研究 WINBASE.H 后您会发现:

typedef RTL_CRITICAL_SECTION CRITICAL_SECTION;

我们将在短时间内揭示 RTL_CRITICAL_SECTION 结构的实质。此时,重要问题在于 CRITICAL_SECTION(也称作
RTL_CRITICAL_SECTION)只是一个拥有易访问字段的结构,这些字段可以由 KERNEL32 API 操作。


在将临界区传递给 InitializeCriticalSection
时(或者更准确地说,是在传递其地址时),临界区即开始存在。初始化之后,代码即将临界区传递给 EnterCriticalSection 和
LeaveCriticalSection API。一个线程自 EnterCriticalSection 中返回后,所有其他调用
EnterCriticalSection 的线程都将被阻止,直到第一个线程调用 LeaveCriticalSection
为止。最后,当不再需要该临界区时,一种良好的编码习惯是将其传递给 DeleteCriticalSection。


在临界区未被使用的理想情况中,对 EnterCriticalSection
的调用非常快速,因为它只是读取和修改用户模式内存中的内存位置。否则(在后文将会遇到一种例外情况),阻止于临界区的线程有效地完成这一工作,而不需要消耗额外的
CPU
周期。所阻止的线程以内核模式等待,在该临界区的所有者将其释放之前,不能对这些线程进行调度。如果有多个线程被阻止于一个临界区中,当另一线程释放该临界区时,只有一个线程获得该临界区。

深入研究:RTL_CRITICAL_SECTION 结构


即使您已经在日常工作中使用过临界区,您也非常可能并没有真正了解超出文档之外的内容。事实上存在着很多非常容易掌握的内容。例如,人们很少知道一个进程的临界区是保存于一个链表中,并且可以对其进行枚举。实际上,WINDBG
支持 !locks
命令,这一命令可以列出目标进程中的所有临界区。我们稍后将要谈到的实用工具也应用了临界区这一鲜为人知的特征。为了真正理解这一实用工具如何工作,有必要真正掌握临界区的内部结构。记着这一点,现在开始研究
RTL_CRITICAL_SECTION 结构。为方便起见,将此结构列出如下:

struct RTL_CRITICAL_SECTION
{
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread;
HANDLE LockSemaphore;
ULONG_PTR SpinCount;
};

以下各段对每个字段进行说明。


DebugInfo 此字段包含一个指针,指向系统分配的伴随结构,该结构的类型为
RTL_CRITICAL_SECTION_DEBUG。这一结构中包含更多极有价值的信息,也定义于 WINNT.H 中。我们稍后将对其进行更深入地研究。


LockCount 这是临界区中最重要的一个字段。它被初始化为数值 -1;此数值等于或大于 0
时,表示此临界区被占用。当其不等于 -1 时,OwningThread 字段(此字段被错误地定义于 WINNT.H 中 — 应当是 DWORD 而不是
HANDLE)包含了拥有此临界区的线程 ID。此字段与 (RecursionCount -1) 数值之间的差值表示有多少个其他线程在等待获得该临界区。


RecursionCount
此字段包含所有者线程已经获得该临界区的次数。如果该数值为零,下一个尝试获取该临界区的线程将会成功。


OwningThread 此字段包含当前占用此临界区的线程的线程标识符。此线程 ID 与
GetCurrentThreadId 之类的 API 所返回的 ID 相同。


LockSemaphore
此字段的命名不恰当,它实际上是一个自复位事件,而不是一个信号。它是一个内核对象句柄,用于通知操作系统:该临界区现在空闲。操作系统在一个线程第一次尝试获得该临界区,但被另一个已经拥有该临界区的线程所阻止时,自动创建这样一个句柄。应当调用
DeleteCriticalSection(它将发出一个调用该事件的 CloseHandle 调用,并在必要时释放该调试结构),否则将会发生资源泄漏。


SpinCount 仅用于多处理器系统。MSDN
文档对此字段进行如下说明:“在多处理器系统中,如果该临界区不可用,调用线程将在对与该临界区相关的信号执行等待操作之前,旋转 dwSpinCount
次。如果该临界区在旋转操作期间变为可用,该调用线程就避免了等待操作。”旋转计数可以在多处理器计算机上提供更佳性能,其原因在于在一个循环中旋转通常要快于进入内核模式等待状态。此字段默认值为零,但可以用
InitializeCriticalSectionAndSpinCount API 将其设置为一个不同值。


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
源码,有注释 // IOCPServer.h: interface for the CIOCPServer class. // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_IOCPSERVER_H__3BBFA68A_31AC_42BB_806B_0D858AF0A861__INCLUDED_) #define AFX_IOCPSERVER_H__3BBFA68A_31AC_42BB_806B_0D858AF0A861__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #pragma once #pragma comment(lib,"Ws2_32.lib") #pragma comment(lib,"Mswsock.lib") #include <winsock2.h> #include <Mswsock.h> #define BUFFER_SIZE 1024*4//I/O请求的缓冲区大小 #define MAX_THREAD 2 //I/O服务器线程数量 //缓冲区对象,它包含了在套接字上处理I/O操作的必要信息 struct CIOCPBuffer { WSAOVERLAPPED ol; SOCKET sClient; //AcceptEx接收的客户套接字 char *buff; //I/0操作使用的缓冲区 int nLen; //buff缓冲区(使用的)大小 ULONG nSequenceNumber;//此I/O的序列号 int nOperation; //操作类型 CIOCPBuffer *pNext; }; //per-Handle数据,它包含了一个套接字的信息 struct CIOCPContext { SOCKET s; //套接字句柄 SOCKADDR_IN addrLocal; //连接的本地地址 SOCKADDR_IN addrRemote;//连接的远程地址 BOOL bClosing; //套接字是否关闭 int nOutStandingRecv; //此套接字上抛出的重叠操作的数量 int nOutStandingSend; ULONG nReadSequence; //安排给接受的下一个序列号 ULONG nCurrentReadSequence;//当前要读的序列号 CIOCPBuffer *pOutOfOrderReads;//记录没有按顺序完成的读I/O CRITICAL_SECTION Lock; //保护这个结构 CIOCPContext *pNext; }; class CIOCPServer //处理线程 { public: CIOCPServer(void); ~CIOCPServer(void); //开始服务 BOOL Start(int nPort=4567,int nMaxConnections=2000, int nMaxFreeBuffers=200,int nMaxFreeContexts=100,int nInitialReads=4); //停止服务 void Shutdown(); //关闭一个连接和关闭所有连接 void CloseAConnection(CIOCPContext *pContext); void CloseAllConnection(); //取得当前的连接数量 ULONG GetCurrentConnection() { return m_nCurrentConnection; }; //向指定客户发送文本 BOOL SendText(CIOCPContext *pContext,char *pszText,int nLen); protected: //申请和释放缓冲区对象 CIOCPBuffer*AllocateBuffer(int nLen); void ReleaseBuffer(CIOCPBuffer *pBuffer); //申请和释放套接字上下文 CIOCPContext *AllocateContext(SOCKET s); void ReleaseContext(CIOCPContext *pContext); //释放空闲缓冲区对象列表和空闲上下文对象列表 void FreeBuffers(); void FreeContexts(); //向连接列表中添加一个连接 BOOL AddAConnection(CIOCPContext *pContext); //插入和移除未决的接受请求 BOOL InsertPendingAccept(CIOCPBuffer *pBuffer); BOOL RemovePendingAccept(CIOCPBuffer *pBuffer); //取得下一个要读取的 CIOCPBuffer *GetNextReadBuffer(CIOCPContext *pContext,CIOCPBuffer *pBuffer); //投递接受I/O,发送I/0,接受I/O BOOL PostAccept(CIOCPBuffer *pBuffer); BOOL PostSend(CIOCPContext *pContext,CIOCPBuffer *pBuffer); BOOL PostRecv(CIOCPContext *pContext,CIOCPBuffer *pBuffer); //事件通知函数 void HandleIO(DWORD dwKey,CIOCPBuffer *pBuffer,DWORD dwTrans,int nError); //建立一个新的连接 virtual void OnConnectionEstablished(CIOCPContext *pContext,CIOCPBuffer*); //一个连接关闭 virtual void OnConnectionClosing(CIOCPContext *pContext,CIOCPBuffer*); //在一个连接上发生错误 virtual void OnConnectionError(CIOCPContext *pContext,CIOCPBuffer*,int nError); //在一个连接上的读操作完成 virtual void OnReadCompleted(CIOCPContext *pContext,CIOCPBuffer*); //在一个连接上写操作完成 virtual void OnWriteCompleted(CIOCPContext *pContext,CIOCPBuffer*); protected: //记录空闲结构信息 CIOCPBuffer *m_pFreeBufferList; CIOCPContext *m_pFreeContextList; int m_nFreeBufferCount; int m_nFreeContextCount; CRITICAL_SECTION m_FreeBufferListLock; CRITICAL_SECTION m_FreeContextListLock; //记录抛出的Accept请求 CIOCPBuffer *m_pPendingAccepts; long m_nPendingAcceptCount; CRITICAL_SECTION m_PendingAcceptsLock; //记录连接列表 CIOCPContext *m_pConnectionList; int m_nCurrentConnection; CRITICAL_SECTION m_ConnectionListLock; //用于投递Accept请求 HANDLE m_hAcceptEvent; HANDLE m_hRepostEvent; LONG m_nRepostCount; //服务器监听端口 int m_nPort; int m_nInitialAccepts; int m_nInitialReads; int m_nMaxAccepts; int m_nMaxSends; int m_nMaxFreeBuffers; int m_nMaxFreeContexts; int m_nMaxConnections; //监听线程 HANDLE m_hListenThread; //完成端口句柄 HANDLE m_hCompletion; //监听套接字句柄 SOCKET m_sListen; //AcceptEx函数地址 LPFN_ACCEPTEX m_lpfnAcceptEx; //GetAcceptExSockaddrs函数地址 LPFN_GETACCEPTEXSOCKADDRS m_lpfnGetAcceptExSockaddrs; //用于通知监听线程退出 BOOL m_bShutDown; //记录服务是否启动 BOOL m_bServerStarted; private://线程函数 static DWORD WINAPI _ListenThreadProc(LPVOID lpParam); static DWORD WINAPI _WorkerThreadProc(LPVOID lpParam); }; #endif // !defined(AFX_IOCPSERVER_H__3BBFA68A_31AC_42BB_806B_0D858AF0A861__INCLUDED_)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值