windows库已经提供了旋转锁的函数,为什么我们还要自己实现呢?因为汇编快啊!
加锁
先定义宏:
#define INTERLOCKED_INCREMENT(A,B) \
__asm mov eax,0 \
__asm mov ecx,A \
__asm mov edx,1 \
__asm lock cmpxchg dword ptr [ecx],edx \
__asm mov B,eax
A代表临界资源当前是否被其他线程占用,只有0和1两种取值;B表示是否需要自旋等待,也只有0和1两种取值。
最关键的是cmpxchg句,cmpxchg指令将累加器AL/AX/EAX/RAX中的值与首操作数(目的操作数)比较,如果相等,第2操作数(源操作数)的值装载到首操作数,zf置1;如果不等, 首操作数的值装载到AL/AX/EAX/RAX并将zf清0。
我们这里不关心zf,总结而言:
当A为0时,首操作数[ecx]与eax相等(都为0),于是将源操作数edx(值为1)装载到首操作数,即将A置为1,以将其他线程拒之门外,最后将B置为0,表示资源抢占成功;
当A为1时,首操作数[ecx]与eax不相等,于是首操作数[ecx]装载到eax,eax值变为1,最后B被置为1,表示资源尚未抢占成功。
光有INTERLOCKED_INCREMENT(A,B)当然还不够,需要做如下封装:
void SpinLockEx(long *SystemSpinLockSection)
{
int k;
INTERLOCKED_TEST_INCREMENT(SystemSpinLockSection,k);
while (k)
{
Sleep(0);
INTERLOCKED_TEST_INCREMENT(SystemSpinLockSection,k);
}
}
如果SystemSpinLockSection为0,跑上来k就被置为0,那么while(k)就被跳过,我们已经抢占到了资源,就不需要自旋等待了;
如果SystemSpinLockSection为1,那么k就被置为1,自旋等待,直到突然有一天,SystemSpinLockSection成了0,那么k变成0就可以跳出循环了。
解锁
也先来段宏:#define INTERLOCKED_DECREMENT(A) \
__asm mov eax,0FFFFFFFFh \
__asm mov ecx,A \
__asm lock xadd dword ptr [ecx],eax
解锁的处理就比加锁简单多了,xadd指令将eax累加到[ecx]中,0FFFFFFFFh的十进制值就是-1,于是INTERLOCKED_DECREMENT(A)就是简单地将A递减,即由1置为0。
接口封装如下:
void SpinUnlockEx(long *SystemSpinLockSection)
{
INTERLOCKED_DECREMENT(SystemSpinLockSection);
}
大功告成~