雕刻时光

just do it……nothing impossible
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

原子操作

Posted on 2014-03-06 16:28  huhuuu  阅读(1064)  评论(0)    收藏  举报

  在多线程的时候,即使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