在多线程的时候,即使i++之类的操作也要谨慎。
观察如下代码:
#include<stdio.h> #include<process.h> #include<windows.h> volatile long g_nLoginCount; const int THREAD_NUM = 50; unsigned int __stdcall ThreadFun(void *pPM){ Sleep(100); g_nLoginCount++; Sleep(50); return 0; } int main(){ g_nLoginCount = 0; HANDLE handle[THREAD_NUM]; int num=20; while(--num){ g_nLoginCount = 0; for(int i = 0;i < THREAD_NUM; i++) handle[i] = (HANDLE)_beginthreadex(NULL,0,ThreadFun,NULL,0,NULL); WaitForMultipleObjects(THREAD_NUM,handle,TRUE,INFINITE); printf("userNum:%d fact: %d\n",THREAD_NUM,g_nLoginCount); } return 0; }

按理说不是应该都是50才正确吗,为什么会这样?

在第二步寄存器eax+1后,可能另外一个线程执行了,eax,dword ptr [g_nLoginCount (417140h)] ,这样的话,eax的值又回到没+1的状态,所以对于这种情况要处理!
有种叫做原子操作,即要么全部实现,要么一个也没有实现。
window下提供了Interlocked系列的原子操作:
如InterlockedIncrement就是自加1的操作,
#include<stdio.h> #include<process.h> #include<windows.h> volatile long g_nLoginCount; const int THREAD_NUM = 50; unsigned int __stdcall ThreadFun(void *pPM){ Sleep(100); InterlockedIncrement(&g_nLoginCount); Sleep(50); return 0; } int main(){ g_nLoginCount = 0; HANDLE handle[THREAD_NUM]; int num=20; while(--num){ g_nLoginCount = 0; for(int i = 0;i < THREAD_NUM; i++) handle[i] = (HANDLE)_beginthreadex(NULL,0,ThreadFun,NULL,0,NULL); WaitForMultipleObjects(THREAD_NUM,handle,TRUE,INFINITE); printf("userNum:%d fact: %d\n",THREAD_NUM,g_nLoginCount); } return 0; }

这样就成功了。
在看看InterlockedIncrement是怎么实现的:
LONG InterlockedIncrement( LONG volatile *value ) { LONG lOriginal, lNewValue; do { //通过load-link读取当前值,并且处理器会监视内存这个值,看看是否发生过变化 lOriginal = load_link(value); // //计算新的值 // lNewValue = lOriginal + 1; // //有条件的写回新值 //如果有人在计算期间覆写该值,则函数返回失败,然后再重新进行上面的计算 } while ( !store_conditional(value, lNewValue)); return lNewValue; }
以下转:
请求CPU监视一个内存地址依赖于CPU自己的实现。但要记住一件事情,CPU在同一时间只能监视一个内存地址,并且这个时间是很短暂的。如果你的代码被抢占了或者在load-link后有一个硬件中断到来,那么你的store-conditional将会失败,因为CPU因为硬件中断而分心了,完全忘记了你要求它监视的内存地址(即使CPU成功的记住了它,也不会记太久,因为硬件中断几乎都会执行自己的load-link指令,因此会替换成它自己要求监视的内存地址)。
另外,CPU可能会有点懒,在监视时并不监视内存地址,而是监视cache line,如果有人修改了一个不同的内存位置,但是刚好跟要被监视的内存地址在同一个cache line里,store-conditional操作也会失败,即使它事实上可以成功完成。ARM架构的CPU是太懒了,以至于任何向同一块2048字节写入的操作都会导致store-conditional失败。
这对于需要用汇编语言来实现Interlocked操作的你来说意味着什么?你需要尽可能减少load-link和store-conditional之间的指令数。例如,InterlockedIncrement只不过是给值加1。你在load-link和store-conditional之间插入的指令越多,store-conditional失败的可能就越大,你就不得不重来一次。如果你在两者之间插入的指令太多了就会导致store-conditional永远不会成功。举一个极端的例子,如果你计算新值的代码需要耗时5秒,在这5秒内肯定会接收到很多硬件中断,store-conditional操作就永远都会失败。
总结:多线程中对于变量普通的加减操作也要注意,用原子操作可以解决这个问题。
参考:http://www.cnblogs.com/pianoid/p/3322060.html,http://blog.csdn.net/morewindows/article/details/7429155
浙公网安备 33010602011771号