临界区:一次只允许一个线程进入直到离开
并发是指多个线程在同时执行:
- 单核–(是分时执行,不是真正的同时)
- 多核–(在某一个时刻,会同时有多个线程再执行)
同步则是保证在并发执行的环境中各个线程可以有序的执行
分析两条线程执行下面代码。
DWORD dwVal =0; //全局变量
线程中的代码:
dwVal ++; //只有一行安全吗?
对应的汇编代码:
mov eax,[0x12345678]
add eax,1
mov [0x12345678],eax
如果在执行完 add eax,1 后产生了线程切换(系统时钟),第二个线程进来了:mov eax,[0x12345678]
add eax,1
mov [0x12345678],eax;
第二个线程已经把1写到全局变量里了,第一个线程突然恢复执行mov [0x12345678],eax写入全局变量里的值还是1。
两线程有序执行结果应该为2,但是它在执行完 add eax,1 后,产生了线程切换得到的数据是错误的。
继续上面把上面的3行汇编换成一行看看是否安全。
inc dword ptr ds:[0x12345678]
如果你的CPU是单核这样的解决方案是安全的,如果你当前的CPU是多核的可能会出现一种极端情况,两个CPU同时执行同一行代码。
将以上代码修改成
lock inc dword ptr ds:[0x12345678] //多核多线程下 依旧安全
lock锁住了当前指令所在的那块内存,用了lock在某个时刻只能有一个CPU来读这块内存。
参考: kernel32.Interlockedincrement
原子操作相关的API:
Interlockedlncrement
InterlockedExchangeAdd
InterlockedFlushSList
InterlockedDecrement
InterlockedExchange
InterlockedPopEntrySList
InterlockedCompareExchange
InterlockedPushEntrySList
多行代码原子操作
关键代码A //N行代码要求原子操作
关键代码B //单独加LOCK可以吗?
关键代码C
如果你的逻辑代码有很多行的话,每个关键代码加lock是没有办法保证的的代码逻辑是正确的。
临界区:一次只允许一个线程进入直到离开
DWORD dwFlag = 0; //11实现临界区的方式就是加锁
//锁:全局变量进去加1 出去减1
if(dwFlag == 0)//判断临界区是否处于打开状态
{
dwFlag = 1; //进入临界区
...
dwFlag = 0; //离开临界区
}
上面的临界区是不安全的,比如线程1判断完临界区是可以进入的,还没有将临界区置1线程切换了,这样就会出现线程1和2同时进入临界区。
自己实现临界区
全局变量: Flag =0
进入临界区:
mov eax,1
lock xadd [Flag],eax
cmp eax,0 //如果不是0就是有人进入临界区了
jz endLab
dec [Flag] //减1 让当前线程等待Sleep..
endLab:
ret
离开临界区:
lock dec[Flag]