关键段(Critical Section)
2015-09-14 14:49
302 查看
关键段
关键段是一小段代码,它在执行之前需要独占对一些共享资源的的访问权。该方式以“原子方式”对资源进行操控。在当前线程离开关键段之前,系统是不会调度任何想要访问同一资源的其他线程的。*注:原子方式,指的是代码知道除了当前线程之外,没有其他任何线程会同时访问该资源。
关键段CRITICAL_SECTION一共就四个函数:
void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection); void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection); typedef struct _RTL_CRITICAL_SECTION { PRTL_CRITICAL_SECTION_DEBUG DebugInfo; // 调试用的 LONG LockCount; // 初始化为-1,n表示有n个线程在等待 LONG RecursionCount; // 表示该关键段的拥有线程对此资源获得关键段次数,初为0 HANDLEOwningThread; // 拥有该关键段的线程句柄,from the thread's ClientId->UniqueThread HANDLE LockSemaphore; // 实际上是一个自复位事件 DWORD SpinCount; // 旋转锁的设置,单CPU下忽略 } RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
线程所有权
关键段是有“线程所有权”概念的。事实上它会用第四个参数OwningThread来记录获准进入关键区域的线程句柄,如果这个线程再次进入,EnterCriticalSection()会更新第三个参数RecursionCount以记录该线程进入的次数并立即返回让该线程进入。其它线程调用EnterCriticalSection()则会被切换到等待状态,一旦拥有线程所有权的线程调用LeaveCriticalSection()使其进入的次数为0时,系统会自动更新关键段并将等待中的线程换回可调度状态。因此可以将关键段比作旅馆的房卡,调用EnterCriticalSection()即申请房卡,得到房卡后自己当然是可以多次进出房间的,在你调用LeaveCriticalSection()交出房卡之前,别人自然是无法进入该房间。
什么时候使用关键段
当我们有一个资源要让多线程访问的时候,应该创建一个CRITICAL_SECTION结构。1、如果有多个总是应该在一起使用的资源,只需创建一个CRITICAL_SECTION结构来保护所有这些结构。
2、如果有多个不总是一起使用的资源–比如,线程1和线程2访问资源A,而线程1和线程3访问资源B,那么应该为每个资源分别创建一个CRITICAL_SECTION结构。
任何要访问共享资源的代码,都必须包含在EnterCriticalSection和LeaveCriticalSection之间。如果忘了哪怕是一个地方,共享资源就有可能被破坏。
关键段怎么用
一般情况下,我们会将CRITICAL_SECTION结构作为全局变量来分配;也可以作为局部变量来分配,或从堆中动态分配;
另外将它们作为类的私有字段来分配也很常见。
class CLockSection { public: CLockSection() { ::InitializeCriticalSection(&_pSec); } ~CLockSection() { ::DeleteCriticalSection(&_pSec); } void Lock() { ::EnterCriticalSection(&_pSec); } void UnLock() { ::LeaveCriticalSection(&_pSec); } private: CRITICAL_SECTION _pSec; }; // 另一种 class CRefCount { public: CRefCount::CRefCount(void) { ::InitializeCriticalSection(&m_refSec); } CRefCount::~CRefCount(void) { ::DeleteCriticalSection(&m_refSec); } inline CRITICAL_SECTION* GetLock() { return &m_refSec; } private: CRITICAL_SECTION m_refSec; }; class CGuard { public: CGuard(CRITICAL_SECTION* pSec) { m_pSec = pSec; if(m_pSec != NULL) { ::EnterCriticalSection(m_pSec); } } ~CGuard() { if(m_pSec != NULL) { ::LeaveCriticalSection(m_pSec); m_pSec = NULL; } } private: CRITICAL_SECTION* m_pSec; }; CRefCount g_ref; // 全局 { CGuard guard(g_ref.GetLock()); /* 需要共享访问的操作 */ } // 离开作用域,自动析构
关键段的优缺点
优点:容易使用,执行速度快(内部也使用了Interlocked函数)缺点:无法用来在多个进程之间对线程进行同步;只能用于互斥,不能用于同步
旋转锁
当线程试图进入一个关键段,但这个关键段正被另一个线程占用的时候,函数会立即把调用线程切换到等待状态。这意味着线程必须从用户模式切换到内核模式(大概1000个CPU周期),这个切换的开销非常大。为了提高关键段的性能,Micsoft把旋转锁合并到了关键段中,当调用EnterCriticalSection的时候,它会用一个旋转锁不断地循环,尝试在一段时间内获得对资源的访问权。只有当尝试失败的时候,线程才会切换到内核模式并进入等待状态。
BOOL InitializeCriticalSectionAndSpinCount( LPCRITICAL_SECTION lpCriticalSection, // pointer to critical section DWORD dwSpinCount) // spin count for critical section (0 ~ 0x00FFFFFF)
建议:
我们应该总是在使用关键段的时候同时使用旋转锁,因为这样做不会损失任何东西。为了得到最佳的性能,最简单的方法是,尝试各种数值,直到对性能感到满意为止。参考值:4000
参考资料
[1] 《Windows核心编程 第五版》[2]秒杀多线程第五篇 经典线程同步 关键段CS
相关文章推荐
- "undefined reference to" 问题解决方法
- Nginx+uWSGI+Django 好文
- This virtual machine is configured for 64-bit guest operating systems. However, 64-bit operation is
- 使用symbolicatecrash翻译crash log日志
- ield 'ClassID' doesn't have a default value 2015-09-14 13:37:25,015 ERROR [org.apache.struts2.dispatcher.Dispatcher] - Exception occurred during processing request: could not execute statement org.hibernate.exception.GenericJDBCException: could not execut
- org.aspectj.lang.JoinPoint解析
- LwIP网络接口结构体---netif
- swift 之url有中文参数
- 端口映射实例说明
- ubuntu下解决解压zip文件中文文件名乱码问题
- 操作系统典型调度算法简介
- java 多线程 ReentrantReadWriteLock 使用
- sql services 2008r2 windows用户下新建用户
- 辛星浅析utf8和utf8mb4
- Mac iOS Json 操作Model to JSON
- cocos2d-x 利用CCLabelTTF制作文字描边与阴影效果的实现方法
- Android中Parcelable接口用法
- Android软键盘弹出时挤压屏幕高度解决办法
- c函数指针详解(转)
- 基于PHP的超炫酷HTML5交互式图表