In t e r l o c k e d E x c h a n g e和I n t e r l o c k e d E x c h a n g e P o i n t e r能够以原子操作方式用第二个参数中传递的值来取代第一个参数中传递的当前值。如果是3 2位应用程序,两个函数都能用另一个3 2位值取代一个3 2位值。但是,如果是个6 4位应用程序,那么I n t e r l o c k e d E x c h a n g e能够取代一个3 2位值,而I n t e r l o c k e d E x c h a n g e P o i n t e r则取代6 4位值。两个函数都返回原始值。当实现一个循环锁时,I n t e r l o c k e d E x c h a n g e是非常有用的:
// Global variable indicating whether a shared resource is in use or not
BOOL g_fResourceInUse = FALSE;
...
void Func1()
{
//Wait to access the resource.
while(InterlockedExchange(&g_fResourceInUse, TRUE) == TRUE)
Sleep(0);
//Access the resource.
...
//We no longer need to access the resource.
InterlockedExchange(&g_fResourceInUse, FALSE);
}
w h i l e循环是循环运行的,它将g _ f R e s o u r c e I n U s e中的值改为T R U E,并查看它的前一个值,以了解它是否是T R U E。如果这个值原先是FA L S E,那么该资源并没有在使用,而是调用线程将它设置为在用状态并退出该循环。如果前一个值是T R U E,那么资源正在被另一个线程使用,w h i l e循环将继续循环运行。
如果另一个线程要执行类似的代码,它将在w h i l e循环中运行,直到g _ f R e s o u r c e I n U s e重新改为FA L S E。调用函数结尾处的I n t e r l o c k e d E x c h a n g e,可显示应该如何将g _ f R e s o u r c e I n U s e重新设置为FA L S E。
当使用这个方法时必须格外小心,因为循环锁会浪费C P U时间。C P U必须不断地比较两个值,直到一个值由于另一个线程而“奇妙地”改变为止。另外,该代码假定使用循环锁的所有线程都以相同的优先级等级运行。也可以把执行循环锁的线程的优先级提高功能禁用(通过调用S e t P r o c e s s P r i o r i t y B o o s t或s e t T h r e a d P r i o r i t y B o o s t函数来实现之)
此外,应该保证将循环锁变量和循环锁保护的数据维护在不同的高速缓存行中(本章后面部分介绍)。如果循环锁变量与数据共享相同的高速缓存行,那么使用该资源的C P U将与试图访问该资源的任何C P U争用高速缓存行。
应该避免在单个C P U计算机上使用循环锁。如果一个线程正在循环运行,它就会浪费前一个C P U时间,这将防止另一个线程修改该值。我在上面的w h i l e循环中使用了S l e e p ,从而在某种程度上解决了浪费C P U时间的问题。如果使用S l e e p,你可能想睡眠一个随机时间量;每次请求访问该资源均被拒绝时,你可能想进一步延长睡眠时间。这可以防止线程浪费C P U时间。根据情况,最好是全部删除对S l e e p的调用。或者使用对S w i t c h To T h r e a d(Windows 98中没有这个函数)的调用来取代它。勇于试验和不断纠正错误,是学习的最好方法。
循环锁假定,受保护的资源总是被访问较短的时间。这使它能够更加有效地循环运行,然后转为内核方式并进入等待状态。许多编程人员循环运行一定的次数(比如4 0 0次),如果对资源的访问仍然被拒绝,那么该线程就转为内核方式,在这种方式下,它要等待(不消耗C P U时间),直到该资源变为可供使用为止。这就是关键部分实现的方法。
循环锁在多处理器计算机上非常有用,因为当一个线程循环运行的时候,另一个线程可以在另一个C P U上运行。但是,即使在这种情况下,也必须小心。不应该让线程循环运行太长的时间,也不能浪费更多的C P U时间。本章后面将进一步介绍循环锁。