多线程必然要考虑同步问题,先举个例子
#include <windows.h>
#include <stdio.h>
DWORD WINAPI WorkThread1(LPVOID param);
DWORD WINAPI WorkThread2(LPVOID param);
long g_x=0;
int main()
{
CloseHandle(CreateThread(NULL,0,WorkThread1,NULL,0,NULL));
CloseHandle(CreateThread(NULL,0,WorkThread2,NULL,0,NULL));
Sleep(100);
printf("%d",g_x);
return 0;
}
DWORD WINAPI WorkThread1(LPVOID param)
{
g_x++;
return 0;
}
DWORD WINAPI WorkThread2(LPVOID param)
{
g_x++;
return 0;
}
这个程序是让g_x自加两次,所以最后的结果可能是2,但是一定是2吗?不一定(虽然不是2的几率比较低)
我们看看g_x++处的汇编代码
17: g_x++;
00401128 mov eax,[g_x (004235bc)]
0040112D add eax,1
00401130 mov [g_x (004235bc)],eax
我们可以看到g_x++是分三步走的
1.把值移到寄存器
2.寄存器的值加1
3.再把寄存器的值移到内存
如果每个线程的g_x++都按这个步骤走,且不打断,没问题
但如果打断呢
mov eax,[g_x (004235bc)]->把内存的值移到寄存器
add eax,1->寄存器的值加1,现在寄存器的值为1了
mov eax,[g_x (004235bc)]->把寄存器的值再次移到寄存器,寄存器值为0
add eax,1->加1,寄存器的值为1
mov [g_x (004235bc)],eax->把寄存器的值写入到内存,内存的值为1
mov [g_x (004235bc)],eax->把寄存器的值写入到内存,内存的值为1
假设WorkThread1先执行,那么粗体部分就是WorkThread2了,我们看到WorkThread1被打断了,现在我们再算算结果是什么
我们看到结果变为1了
这只是一种可能性,经我的测试,结果一直是2,windows核心编程这本书编写很久了,可能作者编写此书时的硬件条件也不一样,导致我无法测试出想要的结果,但是这种风险是存在的。
有没有一种无法打断的加法操作呢,当然有
把g_x++替换为InterlockedExchangeAdd(&g_x,1);
InterlockedExchageAdd.这是一个原子操作,在执行这个函数的过程中绝对不可能被打断,转而去执行另外一个线程的InterlockedExchagedAdd
那么互锁函数是如何运行的呢,对于X86CPU来说,互锁函数会对总线信号发送一个硬件信号,防止另一个CPU访问同一个地址
对于互锁函数,需要了解的是,它的运行速度极快,通常会导致执行几个CPU周期(小于50),而线程从用户太转到内核态需要大概1000个CPU周期
LONG InterlockedExchange(
LPLONG volatile Target,
LONG Value
);
PVOID InterlockedExchangePointer(
PVOID volatile *Target,
PVOID Value
);
这两个函数都能以原子操作方式用第二个参数中传递的值取代第一个参数中的当前值,返回值为第一个参数原来的值,但InterlockedExchange只能用做32位,InterlockedExchangePointer既能用作32位,又能用作64位(指针是void*没有确定的类型,任意类型都能转换)
可以利用InterlockedExchange做循环锁
BOOL g_fResourceInUse=FALSE;
...
void Func1()
{
//等待访问资源
while(InterlockedExchange(&g_fResourceInUse,TRUE)==TRUE)
sleep(0);//当发现g_fResourceInUse原来的值为TRUE时,表示,这个资源正在被别的线程访问,还需要等待,这里为了防止消耗CPU时间,使用了sleep(0)放弃CPU时间,等待重新分配,从一定程度上解决了循环锁CPU时间浪费的问题
//访问资源
...
//我们不再需要访问这个资源,需要把循环锁便量变成FALSE
InterlockedExchange(&g_fResourceInUse,FALSE);
}
注意,使用循环锁的所有线程的优先级最好相同,所以最好禁用掉优先级动态提升(SetProcessPriorityBoost,SetThreadPriorityBoost)
LONG InterlockedCompareExchange(
LPLONG volatile Destination, // 目标地址
LONG Exchange, // 替换的新值
LONG Comperand // 需要去比较的值
);
PVOID InterlockedCompareExchangePointer (
PVOID volatile *Destination, // 目标地址
PVOID Exchange, // 替换的新值所在的地址
PVOID Comperand //需要去比较的值的地址
);
这两个函数都能以原子操作方式用第三个参数中传递的值与第一个参数中的当前值比较,如果相等,就把第二个参数的值赋予第一个参数,返回值为第一个参数原来的值,但InterlockedCompareExchange只能用做32位,InterlockedCompareExchange既能用作32位,又能用作64位(指针是void*没有确定的类型,任意类型都能转换)