锁访问的控制速度非常快,调用一个互锁函数的CPU周期通常小于50,不需要进行用户方式与内核方式的切换(该切换通常需要运行1000个CPU周期)。
互锁访问函数的缺点在于其只能对单一变量进行原子访问,如果要访问的资源比较复杂,仍要使用临界区或互斥。
可等待定时器
可等待定时器是在某个时间或按规定的间隔时间发出自己的信号通知的内核对象。它们通常用来在某个时间执行某个操作。
创建可等待定时器
HANDLE CreateWaitableTimer( PSECURITY_ATTRISUTES psa, BOOL fManualReset,//人工重置或自动重置定时器 PCTSTR pszName);
设置可等待定时器
可等待定时器对象在非激活状态下被创建,程序员应调用 SetWaitableTimer函数来界定定时器在何时被激活:
BOOL SetWaitableTimer( HANDLE hTimer, //要设置的定时器 const LARGE_INTEGER *pDueTime, //指明定时器第一次激活的时间 LONG lPeriod, //指明此后定时器应该间隔多长时间激活一次 PTIMERAPCROUTINE pfnCompletionRoutine, PVOID PvArgToCompletionRoutine, BOOL fResume);
取消可等待定时器
BOOl Cancel WaitableTimer( HANDLE hTimer //要取消的定时器 );
打开可等待定时器
作为一种内核对象,WaitableTimer也可以被其他进程以名字打开:
HANDLE OpenWaitableTimer ( DWORD fdwAccess, BOOL bInherithandle, PCTSTR pszName );
实例
下面给出的一个程序可能发生死锁现象:
#include
运行这个程序,在中途一旦发生这样的输出:
线程1占用临界区1 线程2占用临界区2
或
线程2占用临界区2 线程1占用临界区1
或
线程1占用临界区2 线程2占用临界区1
或
线程2占用临界区1 线程1占用临界区2
程序就\死\掉了,再也运行不下去。因为这样的输出,意味着两个线程相互等待对方释放临界区,也即出现了死锁。
如果我们将线程2的控制函数改为:
long WINAPI ThreadFn(long lParam) { while (TRUE) { EnterCriticalSection(&cs1); printf(\线程2占用临界区1\ EnterCriticalSection(&cs2); printf(\线程2占用临界区2\ printf(\线程2占用两个临界区\ LeaveCriticalSection(&cs1); LeaveCriticalSection(&cs2); printf(\线程2释放两个临界区\ Sleep(20); }; }
再次运行程序,死锁被消除,程序不再挡掉。这是因为我们改变了线程2中获得临界区1、2的顺序,消除了线程1、2相互等待资源的可能性。
由此我们得出结论,在使用线程间的同步机制时,要特别留心死锁的发生。 临界区
定义临界区变量
CRITICAL_SECTION gCriticalSection;
通常情况下,CRITICAL_SECTION结构体应该被定义为全局变量,以便于进程中的所有线程方便地按照变量名来引用该结构体。
初始化临界区
VOID WINAPI InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection //指向程序员定义的CRITICAL_SECTION变量 );
该函数用于对pcs所指的CRITICAL_SECTION结构体进行初始化。该函数只是设置了一些成员变量,它的运行一般不会失败,因此它采用了 VOID类型的返回值。该函数必须在任何线程调用EnterCriticalSection函数之前被调用,如果一个线程试图进入一个未初始化的 CRTICAL_SECTION,那么结果将是很难预计的。
删除临界区
VOID WINAPI DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection //指向一个不再需要的CRITICAL_SECTION变量 );
进入临界区
VOID WINAPI EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection //指向一个你即将锁定的CRITICAL_SECTION变量 );
离开临界区
VOID WINAPI LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection //指向一个你即将离开的CRITICAL_SECTION变量 );
使用临界区编程的一般方法是:
void UpdateData() { EnterCriticalSection(&gCriticalSection); ...//do something LeaveCriticalSection(&gCriticalSection); }
关于临界区的使用,有下列注意点:
(1)每个共享资源使用一个CRITICAL_SECTION变量;
(2)不要长时间运行关键代码段,当一个关键代码段长时间运行时,其他线程就会进入等待状态,这会降低应用程序的运行性能;
(3)如果需要同时访问多个资源,则可能连续调用EnterCriticalSection;
(4)Critical Section不是OS核心对象,如果进入临界区的线程\挂\了,将无法释放临界资源。这个缺点在Mutex中得到了弥补。
互斥
互斥量的作用是保证每次只能有一个线程获得互斥量而得以继续执行,使用CreateMutex函数创建: