Windows事件等待学习笔记(一)—— 临界区&自旋锁
基础知识
并发:是指多个线程在同时执行
- 单核(是分时执行,不是真正的同时)
- 多核(在某一个时刻,会同时有多个线程再执行)
同步:则是保证在并发执行的环境中各个线程可以有序的执行
演示代码
注意:如果线程中的代码只对局部变量进行访问,就不存在并发的问题;只有当访问全局变量的时候,才需要关注并发的问题
案例一
DWORD dwVal = 0; //全局变量
线程中的代码:
dwVal++; //只有一行 安全吗?
答案:
dwVal++; 对应的汇编代码:
mov eax,[0x12345678]
add eax,1 //当第一个线程执行到这里时,产生了线程切换的话
//第二个线程也执行到这里时,将会出现逻辑错误
mov [0x12345678],eax
案例二
当将案例一的三行汇编代码改为一行时:
INC DWORD PTR DS:[0x12345678] //一行汇编指令,安全吗?
答案:若当前系统为单核时,安全;但如果当前CPU为多核时,不安全(两个核有可能同时执行一行指令)
解决方案:改为LOCK INC DWORD PTR DS:[0x12345678]
LOCK
描述:在多核CPU的情况下,保证只有一个CPU能访问带有LOCK指令内存
可以带LOCK前缀的指令:
BT, BTS, BTR, BTC (mem, reg/imm)
XCHG, XADD (reg, mem / mem, reg)
ADD, OR, ADC, SBB (mem, reg/imm)
AND, SUB, XOR (mem, reg/imm)
NOT, NEG, INC, DEC (mem)
单行代码原子操作
原子操作相关API:
InterlockedIncrement InterlockedExchangeAdd
InterlockedDecrement InterlockedFlushSList
InterlockedExchange InterlockedPopEntrySList
InterlockedCompareExchange InterlockedPushEntrySList
InterlockedIncrement 反汇编:
多行代码原子操作
关键代码A //N行代码要求原子操作
关键代码B //单独加LOCK可以吗?
关键代码C
.......
答案:不行,试想一下,当第一个线程执行到关键代码B,第二个线程就执行完关键代码A时,此时虽然第二个线程无法执行关键代码B,但却有可能会先执行关键代码C
解决方案:临界区
临界区
描述:一次只允许一个线程进入直到离开
实现思路:加锁(全局变量)
演示代码
DWORD dwFlag = 0; //实现临界区的方式就是加锁
//锁:全局变量 进去加一 出去减一
if(dwFlag == 0) //进入临界区
{
dwFlag = 1
.......
.......
.......
dwFlag = 0 //离开临界区
}
问题:上述演示代码安全吗?
答案:仍不安全,当第一个线程通过if判断并进入if内部,但还未来得及执行dwFlag=1时,第二个线程也有可能通过if判断从而进入if语句内部
手动实现
进入临界区:
Lab:
mov eax,1
lock xadd [Flag],eax
cmp eax,0
jz endLab
dec [Flag]
//线程等待Sleep..
endLab:
ret
离开临界区
lock dec [Flag]
自旋锁
小提示:单核和多核虽然使用的是同一份内核文件,但里面的代码是有区别的
单核 ntoskrnl.exe 反汇编:
多核 ntoskrnl.exe 反汇编:
分析 KeAcquireSpinLockAtDpcLevel
多核:
单核:
总结
- 自旋锁只对多核有意义
(查看不同版本的KeAcquireSpinLockAtDpcLevel函数) - 自旋锁与临界区、事件、互斥体一样,都是一种同步机制,都可以让当前线程处于等待状态,区别在于自旋锁不用切换线程