单行代码原子操作
多核指令的执行过程
一条指令的执行过程是: CPU先读取指令到CPU内部,然后才是执行该指令。而在多核多线程的情况下。可能出现多个CPU同时读到了同一条指令,这样线程就会出问题。 那么解决访问是,限制CPU读取指令,只能有一个CPU读取同一条指令,该操作成为原子操作。
LOCK 就可以对一个内存加锁,同一时刻只能有一个CPU来读取该内存的指令。
使用临界区,互斥体也可以解决多线程安全问题,但是效率太低。例如临界区,是一个线程进入临界区后把其他线程给挂起来,执行完后再恢复其他线程。这样会对CPU性能造成很大的影响。
而如果把代码改成对访问的公共内存加锁,这样就可以提高效率。还能保证多线程安全不会出问题。
for (int i=0;i<10000000;i++)
{
__asm {
lea eax, a //这里使用取地址,就是为了对内存加锁 而不能用mov eax,a 对寄存器加锁是没有意义的,因为多核情况下有多个eax寄存器.
lock inc dword ptr ds : [eax] //这里限制了,在同一时刻只能有一个CPU执行这一行代码,去掉lock就会发生问题
//inc dword ptr ds : [eax] 如果写成这样,单核是安全的 ,多核就会出现问题,可能有两个CPU同时把读取了a的值,读取的时候是一样的值,而执行完这条指令,结果只+1。
}
}
临界资源
全局变量,文件 这种不能让多个CPU同时来操作的资源就是临界资源。
多行代码原子操作
自己实现临界区
#include <windows.h>
#include <stdio.h>
DWORD dwFlag = 0;
__declspec(naked) void ScritiSection()
{
__asm {
start: mov eax, 1
lock xadd[dwFlag], eax //必须要使用这种读取值,和设置值在一条指令实现的汇编指令。
cmp eax, 0
jz endLab
lock dec[dwFlag]
}
Sleep(1);
__asm {
jmp start
EndLab:
ret
}
}
void LeaveSecritiSection()
{
__asm {
lock dec[dwFlag]
}
}
int a = 0;
void fun1(void* p)
{
for (int i = 0; i < 1000011; i++)
{
ScritiSection();
a++; //临界区代码 访问临界资源
LeaveSecritiSection();
}
}
void fun2(void* p)
{
for (int i = 0; i < 1000000; i++)
{
ScritiSection();
a++;
LeaveSecritiSection();
}
}
int main(int argc, char * argv[])
{
DWORD tid1;
DWORD tid2;
HANDLE hThread[2] = { 0 };
hThread[0]=CreateThread(0, 0, (LPTHREAD_START_ROUTINE)fun1, 0, 0, &tid1);
hThread[1] = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)fun2, 0, 0,&tid2);
WaitForMultipleObjectsEx(2, hThread,TRUE,INFINITE,0);
printf("a=%d\n", a);
getchar();
return 0;
}
总结:
临界区的本质就是线程切换(Sleep的本质就是主动切换线程),非常消耗资源。更加轻量级,效率更高的解决方案是使用自旋锁。