本篇文章参考 windows核心编程 P215
下图这个简单的测试会产生1个,2个,4个线程,这些线程会使用不同的线程同步机制来重复执行相同的任务,在双核处理器上执行结果如下:
线程/微秒 volatile读取 volatile写入 interlocked递增 关键段 SRWLock共享模式 SRWLock独占模式 互斥量
1 8 8 35 66 66 67 1060
2 8 76 153 268 134 148 11082
4 9 145 361 768 244 307 23785
1. 读取一个volatile长整形值
LONG lValue = value;
volatile 读取非常快,因为它不需要进行任何同步,直接从内存中取出数据,与CPU高速缓存完全无关
2. 写入一个volatile值
单线程的时候只有8微秒,两个线程的时候,我们很显然认为执行相同的操作,时间只不过是加倍,但是实际在双处理器的机器上时间消耗的更多,为76。这是因为CPU之间必须相互通信,保证缓存的一致性,四个线程时,时间加了一倍,因为在双线程时,消耗的时间已经考虑到了cpu之间的相互通信,这里仅仅是工作量加倍。
如果cpu数量增加,消耗的时间还会增加,因为相互通信花费的时间更长。
3. 使用interlockedIncrement 来安全地递增一个volatile值
InterlockedIncrement(&value);
interlockedIncrement 要比volatile读写都要慢,因为它要保证在同一时刻cpu之间只有一个cpu能够访问并修改它。使用两个线程比使用一个线程慢的多,因为cpu之间需要通信,来保证告诉缓存的一致性。4个线程更慢,因为工作量加倍。 如果在4个cpu的机器上 性能可能更糟,因为要在4个cpu之间进行通信。
4. 使用关键段来读取volatile长整形值
关键段比较慢,因为存在进入和离开两个过程。 此外,进入和离开关键段需要修改CRITICAL_SECTION结构中的多个字段,所以当发生争夺现象的时候,关键段执行要慢得多。4个线程时,花费的时间超过了2个线程的两倍还多,因为上下文的切换增大了资源争夺的可能性。
5. SRWLock 来读取volatile长整型值
当只有一个线程的时候,SRWLock 执行读取和写入操作的时间几乎相同。
当有两个线程的时候,SRWLock 执行读操作性能要优于写操作,因为读操作可以同时读取,而写操作是互斥的。 与四个线程较为相似。
此外,由于多个线程会不断地写入锁的字段以及它保护的数据,因此各cpu必须在他们的高速缓存之间来回传输数据。
6. 使用内核对象 互斥量
性能最差,这是因为等待互斥量以及后来释放互斥量需要线程每次都在用户态和内核态之间来回进行切换,而这种切换的时间消耗是非常巨大的,在两个线程或者四个线程执行时,性能会急剧下降。
如果我们希望在应用系统中得到最佳性能,首先应该尝试不要使用共享数据,然后依次使用volatile读取,volatile写入,Interlocked API, SRWLock 以及 关键段。 如果都不能满足要求的话,在去尝试使用内核对象。