1、互锁函数家族
- InterlockedExchangeAdd, InterlockedExchangePointer, InterlockedExchangeAdd64
- InterlockedExchange, InterlockedExchangePointer, InterlockedExchangeAdd64
- InterlockedCompareExchange, InterlockedCompareExchangePointer, InterlockedCompareExchange64
- 对于互锁函数,需要了解的另一个重要问题是,它们运行的速度极快。调用一个互锁函数通常会导致执行几个CPU周期(通常小于50),并且不会从用户方式转换为内核方式(通常这需要执行1000个CPU周期)
- 当实现一个循环锁时,InterlockedExchange是非常有用的,但是必须格外小心,因为循环锁会浪费C P U时间。C P U必须不断地比较两个值,直到一个值由于另一个线程而“奇妙地”改变为止。另外,该代码假定使用循环锁的所有线程都以相同的优先级等级运行。也可以把执行循环锁的线程的优先级提高功能禁用(通过调用SetProcessPriority Boost或setThreadPriorityBoost函数来实现之)。此外,应该保证将循环锁变量和循环锁保护的数据维护在不同的高速缓存行中。如果循环锁变量与数据共享相同的高速缓存行,那么使用该资源的C P U将与试图访问该资源的任何C P U争用高速缓存行。
2、高速缓存行(针对多处理器计算机)
- 高速缓存行由3 2或6 4个字节组成(视CPU而定),并且始终在第3 2个字节或第6 4个字节的边界上对齐。高速缓存行的作用是为了提高CPU运行的性能。通常情况下,应用程序只能对一组相邻的字节进行处理。如果这些字节在高速缓存中,那么CPU就不必访问内存总线,而访问内存总线需要多得多的时间。
- 应该将高速缓存行存储块中的和高速缓存行边界上的应用程序数据组合在一起。这样做的目的是确保不同的CPU能够访问至少由高速缓存行边界分开的不同的内存地址。还有,应该将只读数据(或不常读的数据)与读写数据分开。同时,应该将同一时间访问的数据组合在一起。
- 最好是始终都让单个线程来访问数据(函数参数和局部变量是确保做到这一点的最好方法),或者始终让单个CPU访问这些数据(使用线程亲缘性)。如果采取其中的一种方法,就能够完全避免高速缓存行的各种问题。
3、Volatile BOOL g-fFinishedCalculation = FALSE;
Volatile类型的限定词。它告诉编译器,变量可以被应用程序本身以外的某个东西进行修改,这些东西包括操作系统,硬件或同时执行的线程等。尤其是,Volatile限定词会告诉编译器,不要对该变量进行任何优化,并且总是重新加载来自该变量的内存单元的值。另外,使一个结构具备易变性,可以确保它的所有成员都具有易变性,当它们被引用时,总是可以从内存中读取它们。
4、关键代码段
- CRITICAL_SECTION
- InitializeCriticalSection(...), DeleteCriticalSection(...)
- InitializeCriticalSectionAndSpinCount(...), SetCriticalSectionSpinCount(...)
- EnterCriticalSection(...), LeaveCriticalSection(...)
- TryEnterCriticalSection(...)
- 可以想象,只要有一个线程表现出这种相当粗暴的行为,资源就会遭到破坏。
- 关键代码的主要缺点是无法用它们对多个进程中的各个线程进行同步。不过在第1 9章中,我将要创建我自己的同步对象,称为Optex。这个对象将显示操作系统如何来实现关键代码段,它也能用于多个进程中的各个线程。
5、关键代码段 - tips
- 每个共享资源使用一个CRITICAL_SECTION变量
- 同时访问多个资源时,必须始终按照完全相同的顺序请求对资源的访问。注意,当调用LeaveCriticalSection函数时,按照什么顺序访问资源是没有关系的,因为该函数决不会使线程进入等待状态
- 不要长时间运行关键代码段