我使用来自 Windows Via C / C ++ 的代码在Windows 7下测试了瘦读取器/写入器锁的性能。
结果让我感到惊讶的是,独家锁定了共享的性能。这是代码和结果。
unsigned int __stdcall slim_reader_writer_exclusive(void *arg)
{
//SRWLOCK srwLock;
//InitializeSRWLock(&srwLock);
for (int i = 0; i < 1000000; ++i) {
AcquireSRWLockExclusive(&srwLock);
g_value = 0;
ReleaseSRWLockExclusive(&srwLock);
}
_endthreadex(0);
return 0;
}
unsigned int __stdcall slim_reader_writer_shared(void *arg)
{
int b;
for (int i = 0; i < 1000000; ++i) {
AcquireSRWLockShared(&srwLock);
//b = g_value;
g_value = 0;
ReleaseSRWLockShared(&srwLock);
}
_endthreadex(0);
return 0;
}
g_value
是一个全局int volatile变量。
您能否解释为何会发生这种情况?
答案 0 :(得分:19)
对于小型通用锁(如SRWLocks,它只是一个指针大小),这是一个非常常见的结果。
关键点:如果你有一个非常小的保护代码部分,这样锁本身的开销可能占主导地位,那么使用独占锁比使用共享锁更好。
此外,Raymond Chen关于g_Value争论的论点也是正确的。如果在两种情况下都读取了g_Value而不是写入,那么您可能会注意到共享锁的好处。
<强>详细信息:强>
SRW锁使用单个指针大小的原子变量实现,该变量可以采用多种不同的状态,具体取决于低位的值。这些位的使用方式的描述超出了这个注释的范围 - 状态转换的数量非常高 - 所以,我只提到你在测试中可能遇到的一些状态。
初始锁定状态:(0,ControlBits:0) - SRW锁定以所有位设置为0开始。
共享状态:(ShareCount:n,ControlBits:1) - 当没有冲突的独占获取且锁保持共享时,共享计数直接存储在锁变量中。 / p>
独占状态:(ShareCount:0,ControlBits:1) - 当没有冲突的共享获取或独占获取时,锁定的位设置为低,没有别的。
示例争用状态:(WaitPtr:ptr,ControlBits:3) - 当发生冲突时,等待锁定的线程使用在等待线程上分配的数据形成队列栈。 lock变量存储指向队列尾部的指针,而不是共享计数。
在这个方案中,当你不知道初始状态时尝试获取一个独占锁是对锁定字的单次写入,设置低位并检索旧值(这可以在x86上用一个LOCK BTS指令)。如果你成功了(正如你在1线程案例中所做的那样),你就可以进入锁定区域而不需要进一步的操作。
尝试获取共享锁是一个更复杂的操作:您需要首先读取锁变量的初始值以确定旧的共享计数,增加您读取的共享计数,然后有条件地写回更新的值LOCK CMPXCHG指令。这是一个明显更长的串行相关指令链,因此速度较慢。 CMPXCGH在许多处理器上比LOCK BTS等无条件原子指令慢一点。
理论上可以通过假设锁在开始时处于初始状态并首先执行LOCK CMPXCHG来加速锁的第一次共享获取。这将加速锁的初始共享获取(所有这些都在您的单线程情况下),但它会显着减慢锁已经共享并发生第二次共享获取的情况。
当释放锁时会发生类似的一组不同操作,因此管理共享状态的额外成本也会在ReleaseSRWLockShared端支付。
答案 1 :(得分:4)
致力于优化Windows锁定的Windows内核开发人员告诉我关于性能的经验:
显然,人们应该考虑锁的其他方面:
所以是的,只是赞成CS除非读取&gt;&gt;&gt;&gt;写入。
答案 2 :(得分:0)
猜测:
独占锁是更简单的情况。共享锁启用并行性,但需要处理饥饿的可能性,因此会产生额外的开销。