windows线程同步之关键段(critical section)
下面是一段(共享资源被破坏)示例代码
//两个线程的共享资源
const int COUNT = 10;
INT g_nSum = 0;
CRITICAL_SECTION g_cs;
//线程1
DWORD WINAPI FirstThread(PVOID pvParam)
{
EnterCriticalSection(&g_cs);
g_nSum = 0;
for (int n = 1; n<= COUNT; n++)
{
g_nSum += n;
}
LeaveCriticalSection(&g_cs);
return g_nSum;
}
//线程2
DWORD WINAPI SecondThread(PVOID pvParam)
{
EnterCriticalSection(&g_cs);
g_nSum = 0;
for (int n = 1; n <= COUNT; n++)
{
g_nSum += n;
}
LeaveCriticalSection(&g_cs);
return g_nSum;
}
我们先在代码中定义一个g_cs的CRITICAL_SECTION数据结构,然后把任何需要访问的共享资源(这里g_nSum)的代码放在EnterCriticalSection和LeaveCriticalSection之间。如果忘了哪怕是一个地方,共享资源就有可能被破坏。
通过 这个函数对CREATE_SECTION结构成员进行初始化,由于这个函数只是设置一些成员变量而已,不可能失败,因此返回值是void。
2、当知道线程不在需要访问共享资源的时候,我们应该调用下面的函数来清理CREATE_SEATION结构:
DeleteCriticalSection(&g_cs);
DeleteCriticalSection会重置结构成员中的变量。很自然,如果还有线程正在使用一个关键段,那么当然不应该删除它。
3、EnterCriticalSection函数会检查CREATE_SEATION结构中的成员变量,这些变量表示是否有线程正在访问资源,以及哪个线程正在访问资源。
如果没有线程在访问资源,那么EnterCriticalSection会更新成员变量,表示调用的线程已获得对资源的访问,并立即返回。
如果成员变量表示调用线程已经获准访问资源,那么EnterCriticalSection会更新变量,以表示调用线程被获准访问的次数,并立即返回,这种情况非常少见,只有当线程调用LeaveCriticalSection之前连续调用EnterCriticalSection两次以上才会发生。
如果成员变量有一个(调用线程之外的其他)线程已经获准访问资源,那么EnterCriticalSection会使用一个事件内核对象来把调用线程切换到等待状态,这不会浪费任何CPU的时间,系统会记住这个线程要访问这个资源,一旦当前正在访问资源的线程调用了LeaveCriticalSection,系统会自动更新CREATE_SEATION的成员变量并将等待中的线程切换回可调度状态。
实际上对EnterCriticalSection的调用最终会超时并引发异常。导致超时的时间长度用下面的这个注册表子项中包含的CriticalSectionTimeout值决定:HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager这个值以秒为单位,默认值是2592000秒,大约30天
4、我们可以用TryEnterCriticalSection来代替EnterCriticalSection
BOOL TryEnterCriticalSection(PCREATE_SECTION pcs);
这个函数不会让调用线程进入等待状态,它会通过返回值来表示调用线程是否获准访问资源。因此如果TryEnterCriticalSection发现资源正在被其他线程访问,那么它会返回FALSE,其它情况下,它会返回TRUE。每个返回TRUE的TryEnterCriticalSection调用必须有一个对应的LeaveCriticalSection。
5、LeaveCriticalSection会检查CREATE_SECTION内部结构成员变量并将计数器减1,计数器代表调用线程获准访问共享资源的次数,如果计数器大于0,LeaveCriticalSection会直接返回,不执行任何操作。