线程间的同步控制
前言:
撰写多线程程序的一个最具挑战性的问题是:如何让一个线程和另一个线程合作,即如何进行进程和线程的协调工作,线程间的协调工作是由同步机制完成的,同步机制相当于线程之间的红绿灯,我们可以设计让一组线程使用同一个红绿灯系统,这个红绿灯系统必须达到如下两个目的:
1) 这个红绿灯系统负责给某个线程绿灯而给其他线程红灯
2) 这组红绿灯系统必须确保每一个线程都有机会获得绿灯
有很多种同步机制可以运用,使用哪一种同步机制完全视欲解决的问题而定,我们这节讨论每一种同步机制的使用方法,并对每一种同步机制,分析”为什么使用”及”何时使用”
Critical Sections(关键区/临界区)
Critical section是Win32中比较容易使用的一个同步机制,它是WINNT.H定义的一种结构体类型的变量,并不是核心对象,存在于进程的内存空间中,适用范围是单一进程的各线程之间,不能用于同步不同进程间的线程,下面我们对Critical section同步的机制及过程进行分析
1.CRITICAL_SECTION简介
临界区是一种轻量级机制,在某一时间只允许一个线程执行某个给定代码段,通常在修改全局数据时使用临界区,事件,信号量也用于多线程同步,但临界区与他们不同,它不总是执行向内核模式转换,速度非常快,但它的缺点在于临界区只能用于对同一进程内的线程进行同步
使用临界区的过程:
在将临界区传递给InitializeCriticalSection时,临界区开始存在,初始化之后,代码即将临界区传递给EnterCriticalSection和LeaveCriticalSection API,当一个线程自EnterCriticalSection中返回后,所有其他调用EnterCriticalSection的线程都将被阻止,知道该线程调用LeaveCriticalSection为止,最后,当不需要临界区时,一种良好的编码习惯是将其传递给DeleteCriticalSection
2.使用临界区时用到的API
1) VOID InitalizeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
功能:初始化一个critical section对象
参数:lpCriticalSection 一个指针,指向欲被初始化的CRITICAL_SECTION对象,这个变量应该事先在程序中定义
返回:VOID
2) VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSecion);
参数:lpCriticalSeciton 指向一个不再需要的CRITICAL_SECTION变量
返回:返回VOID
3) VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
参数:lpCriticalSection 指向一个你即将锁定的CRITICAL_SECTION变量
返回值:返回VOID
4) VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
参数:lpCriticalSection 指向一个即将解除锁定的CRITICAL_SECTION变量
返回值:返回VOID
3.临界区进阶理解
很多人对CRITICAL_SECTION的理解是错误的,认为CRITICAL_SECTION锁定了资源,其实CRITICAL_SECTION是不能够锁定资源的,它是通过修改自身的状态来来达到线程间的同步的,简单的说,当一个线程执行了EnterCriticalSection之后,cs的状态被修改了,此时没有任何资源被锁定,不管什么资源,其他线程还是可以访问的,只不过,在这个线程尚未执行LeaveCriticalSection()之前,其它线程碰到EnterCriticalSection语句的话,就会处于等待状态,即CRITICAL_SECTION通过修改自身的状态实现同步共享资源的效果,所以,使用中我们必须把每一个线程中访问共享资源的语句放在EnterCriticalSection和LeaveCriticalSection之间。
错误的程序段:
第一个线程函数:
DWORD WINAPI ThreadFuncA(LPVOID lp)
{
EnterCriticalSection(&cs);
...
// 操作共享资源
...
LeaveCriticalSection(&cs);
return 0;
}
第二个线程函数:
DWORD WINAPI ThreadFuncB(LPVOID lp)
{
...
// 操作共享资源
...
return 0;
}
这里,当第一个线程访问共享资源时,第二个线程因为其未被阻塞,所以依然可以访问共享资源
正确代码:
我们需要在第二个线程访问共享资源的前后加上EnterCriticalSection(&cs)和LeaveCriticalSection(&cs),如下:
DWORD WINAPI ThreadFuncA(LPVOID lp)
{
EnterCriticalSection(&cs);
...
// 操作共享资源
...
LeaveCriticalSection(&cs);
return 0;
}
我们要理解,CRITICAL_SECTION不是针对资源的,而是针对不同线程间的代码段的,它通过设置自身状态,来达到同步访问共享资源的目的,使用时,我们在进程范围内,定义一个全局变量CRITICAL_SECTION,在任何访问共享资源的地方加入EnterCriticalSection和LeaveCriticalSection语句,使得同一时间只能够有一个线程的代码段访问到共享资源。
4.CRITICAL_SECTION使用总结
1) 不是核心对象,使用时不需要进入操作系统核心模式,直接在"user mode"下就可以进行操作,同步时执行时间更快
2) 只能用于同一进程的多个线程同步访问共享变量,不能同步不同进程的线程
3) 使用时要将其定义为CRITICAL_SECTION类型的全局变量,不能定义为主线程的局部变量,因为这样会导致其他线程中该变量未定义
4) 它是通过修改自身的状态达到多个线程同步的目的,而不是针对锁定共享资源达到同步目的
5) 在需要访问共享资源的所有线程的代码前后加上EnterCriticalSeciont()及LeaveCriticalSection()