这里假定所有使用旋转锁的线程都是以相同的优先级运行的。但是对于单cpu的系统来说使用旋转锁是没有意义的。此处在检测到资源被占用时使用了sleep,可以睡眠一定随机的时间,这在一定程度上缓解了这种状况。

  旋转锁假定被保护的资源只会被占用一段时间,与切换到内核模式然后等待相比,这种情况下以循环方式进行等待的效率会更高。许多开发人员会指定循环的次数,如果届时仍然无法访问资源,那么线程会切换到内核模式,并一直等待到资源可供使用为止。这是关键段的使用方式。

  在多处理器上使用旋转锁很有用,这是因为当一个线程在一个cpu上运行时,另一个线程可以再另一个cpu上循环等待。再次提醒,在单cpu的系统上循环锁毫无意义。

  当cpu从内存中读取一个字节的时候,它并不是从内存中读取一个字节,而是取回一个高速缓存行。高速缓存行可能是32字节或是64字节。这取决于cpu。高速缓存行存在的目的是为了提高性能。这是根据程序的局部性原理。如果所有字节都在高速缓存行内那么cpu不需要访问内存总线。

  对于多处理器环境,高速缓存使得对内存的更新变得更加困难:

  1:cpu1读取一个字节,这使得该字节以及与它相邻的字节被读取到cpu1的高速缓存中。

  2:cpu2读取到同一字节,这使得该字节被读到cpu2的高速缓存中。

  3:cpu1对内存中的这个字节进行修改,这使得该字节被写入到cpu1的告诉缓存中,并没有写回内存。

  4:cpu2再次读取到同一字节。由于该字节已经在cpu2的高速缓存中,因此cpu2不需要在访问内存。但cpu2无法看到该字节在内存中的新值。

  上述情况非常糟糕。但是cpu芯片的设计者早考虑到了这种情况。当一个cpu修改了高速缓存行的一个字节时,机器中的其他cpu会收到通知,并将自己的高速缓存行作废。因此在刚才的情形中,cpu2的高速缓存作废了。Cpu1必须将它的高速缓存写回内存,cpu2必须重新访问内存来填满它的高速缓存行。我们可以看到虽然高速缓存可以提高性能,但是在多处理器机器上它同样能够损伤性能。

  为了提高性能,我们应该根据高速缓存的大小来将应用程序的数据组织在一起,将数据与缓存行的边界对齐。并把只读数据与可读写数据分别存放。

  使用GetLogicalProcessorInformaiton函数可以获得cpu高速缓存行的大小。我们可以使用 _declspec(align(#))指示符来对字段对齐加以控制。说了那么多的措施,其实好的方法是始终让一个cpu访问数据或只让一个线程访问数据可以完全避免高速缓存行的问题。

  如果我们只需要以原子方式修改一个值,那么Interlock系列函数非常好用。但是大多数情况下我们需要处理的数据结构往往要比一个简单的32位值或64位值复杂的多。为了能够以原子的方式来访问复杂的数据结构,我们可以使用windows使用的其他特性。

  使用旋转锁不停的访问是非常糟糕的一种方式,好的情况下是当线程想要访问共享资源时,线程必须调用操作系统的一个函数,告诉操作系统自己等待什么资源。如果此时资源不可用,此线程将会进入等待状态,不可被调度。如果操作系统检测到资源已经可以使用了,系统会将此线程设为可调度状态并访问此资源。

  volatile关键字告诉编译器这个变量可能会被应用程序之外的其他东西修改,不要对该变量做任何形式的优化,而是始终从内存中读取此值。如果不对一个变量加上volatile限定符,编译器可能会对C++代码进行优化,如它会将变量值载入到寄存器中,以后的操作都是对寄存器中的值进行操作,并不会多次访问内存。对一个结构加上volatile限定符等于给结构中的所有成员都加入volatile限定符。

  关键段(Critical Section)

  关键段是一小段代码,它在执行之前需要独占对一些共享资源的访问权。这种方式可以让多行代码以原子方式访问,在当前线程离开关键段之前,系统不会调度任何其他线程访问该关键段。

  比如在一个链表管理的例子中,如果对链表的访问没有同步,那么一个线程可能会在另一个线程在链表中查询时向链表添加元素。如果两个线程同时向链表中添加元素情况会更糟。而使用关键段可以有效地防止以上各种情况。