关于多线程原子操作的一个疑问?

时间:2021-11-12 18:31:57
看到书上说k=k+1这样的操作并不是原子操作,用汇编语言解释就是要经过mov,add,mov之类的操作之后才能完成加1的操作,如果在多线程环境下,有可能在其中的一步就会被别的进程影响(不然还要InterlockedIncrement()之类的函数干啥),,因此我认为在多线程环境下结果应该小于理论结果,但是我经过编程测试却发现即使在多线程环境下,结果却和理论结果一样!!!一下是我的测试程序:
线程类:
 {
class thread1 : public TThread
{
private:
protected:
        void __fastcall Execute();
public:
   unsigned  int count;            //count用来记录累加次数
         __fastcall qin1(bool CreateSuspended);
};
}
 线程程序:
   void __fastcall thread1::Execute()
{while(1)
 {k=k+1;                    //  k为全局变量,所有线程对改变量进行累加,32位数
 this->count=this->count+1;  //记录累加次数
}

主程序:
{
qq1=new thread1(true);
qq1->count=0;
 qq2=new thread1(true);
qq2->count=0;
qq1->Resume();
qq2->Resume();
qq1->Suspend();
qq2->Suspend();
unsigned int p=qq1->count+qq2->count;  //纪录两个线程所进行的累加次数
。。。。
}

如果按照非原子操作理论,最后的k值应该小于或等于p,但实际上却大于等于p,而且差距小于等于2(属于正常范围),也就是表示:在一个线程的k=k+1中,没有被另外的线程中断。这是为什么呢?!!!!我的环境是:winxp,cb6,32位对齐方式!!!!!

23 个解决方案

#1


不好意思,程序有点点小改动,应该是:
线程类:
 {
class thread1 : public TThread
{
private:
protected:
        void __fastcall Execute();
public:
   unsigned  int count;            //count用来记录累加次数
         __fastcall qin1(bool CreateSuspended);
};
}
 线程程序:
   void __fastcall thread1::Execute()
{while(1)
 {k=k+1;                    //  k为全局变量,所有线程对改变量进行累加,32位数
 this->count=this->count+1;  //记录累加次数
}

主程序:
{
qq1=new thread1(true);
qq1->count=0;
 qq2=new thread1(true);
qq2->count=0;
qq1->Resume();    //开始线程
qq2->Resume();
}

。。。。。       //经过一段时间后,结束线程         
{
qq1->Suspend();
qq2->Suspend();
unsigned int p=qq1->count+qq2->count;  //纪录两个线程所进行的累加次数
。。。。
}

如果按照非原子操作理论,最后的k值应该小于或等于p,但实际上却大于等于p,而且差距小于等于2(属于正常范围),也就是表示:在一个线程的k=k+1中,没有被另外的线程中断。这是为什么呢?!!!!我的环境是:winxp,cb6,32位对齐方式!!!!!

#2


你的测试本身就有问题,
qq1->Suspend(); //线程执行到哪里挂起的? 答案是不确定
qq2->Suspend(); //同样
所以k和p没有可比性.
可以用Terminate停止线程后再比较.

#3


不错,我们不知道线程执行到哪步被挂起!!但是我们可以知道,如果k=k+1没有被中断的话,那么p和k的值不会超过2!!!!!!我们来看看挂起的可能的两个两种情况,要么完成了k=k+1,要么完成了k=k+1和this->count=this->count+1,如果是第一种情况,那么k的值等于count+1,所以说,p和k的值不会超过2(因为有两个线程,而且只有在最后一个线程周期才会被suspend()!!)

#4


mark

#5


k=k+1在现代的编译器中通常编译为Inc [k],可以认为是原子操作。

#6


或者我们变换一下程序:
线程程序:
   void __fastcall thread1::Execute()
{
 unsigned int i=0;
 while(i<400000000)
 {k=k+1;
 i=i+1;
 }
}                   //  k为全局变量,所有线程对改变量进行累加,32位整数
 

当两个线程执行完后,得到的结果是:k=400000000,恰好等于两个线程累加的次数,这难道不足以说明k=k+1这个操作是按照原子操作来完成的么?另外,如果我的全局变量k为64位数(unsigned __int64),那么,得到的结果k与400000000这个值相去廷远的!!!表示对于k=k+1来说,64位数操作不是原子操作!!!而32位数则是一个原子操作!!!但是从汇编的角度来说,全都应该不是原子操作,不知道大家还有没有不同的看法!!!!!???????

#7


不好意思,k的结果应该是800000000,我打错了!!!!!

#8


如果说k=k+1是原子操作,那为什么ms还要特意搞个InterlockedIncrement函数来进行加1原子操作呢,而且还指明只能对32位数使用!!!!!!

#9


InterlockedIncrement的实现不过就是:
lock
inc [address]
使用lock是为了在多处理器系统中独占总线。
仅仅使用inc指令在单处理器系统中是可以算是原子操作的,但是对多处理器系统就存在潜在的问题了。

#10


请问在哪可以看到这方面的资料,我是说有没有权威的证明指出:inc指令在单处理器系统中是可以算是原子操作的,但是对多处理器系统就存在潜在的问题了!!而在多处理器中为何会存在问题,可以具体说说么!!!!!

#11


我觉得不是Inc [k]的原因,因为如果我把程序改为k=k+2;或者k=k+3;这样总不会还编译成Inc [k]了巴,可是结果还是和单线程的结果一样,累加赋值操作并没有被中断!!!!!

#12


而且我把k=k+1改为k=k+s;(s=1)结果也是一样,k=k+s在汇编中不可能一步完成巴!!!!难道不是在c++builder不是在汇编这一层实现多线程的?!!!!!

#13


k=k+s编译后的指令是:
add [k],s  ;s是立即数
仍然是一条指令。

在多处理器系统中存在潜在问题的原因是:
不使用LOCK指令前缀锁定总线的话,在一次内存访问周期中有可能其他处理器会产生异常或中断,而在异常处理中有可能会修改尚未写入的地址,这样当INC操作完成后会产生无效数据(覆盖了前面的修改)。

#14


楼主搞错,k=k+1不是原子操作(即使在单处理器系统中)。而且楼主的推理前提就是有问题的。
不是说不是原子操作就肯定会计算错误,而是说存在这个可能。
另:"如果按照非原子操作理论,最后的k值应该小于或等于p,但实际上却大于等于p",我不知道楼主是怎么测试到k值实际上却大于等于p的。
实际上当操作次数很少时,确实K=K+1确实结果不会错,看起来象是原子操作。

#15


难道没看到我的程序么?实际上我的结果就是k大于等于p,如果是后面那一个程序,实际结果就是累加的次数,一点都没有少,也不多!!!!如果说这样的程序还不能检测出原子操作,那么还有什么办法可以检测出不是原子操作呢?!!!!!

#16


看了一下,感觉你的测试代码这么写是有问题。
//.h
class MyThread : public TThread
{
private:
               
protected:
    void __fastcall Execute();
public:
    __fastcall MyThread(bool CreateSuspended);
    unsigned  int count;//count用来记录累加次数

//.cpp
unsigned __int64 k;//  k为全局变量,所有线程对改变量进行累加

__fastcall MyThread::MyThread(bool CreateSuspended)
    : TThread(CreateSuspended)
{
    count = 0;
}
//---------------------------------------------------------------------------
void __fastcall MyThread::Execute()
{
    //---- Place thread code here ----
    while( count < 400000000 )
    {              
        k = k + 1;
        count = count + 1;  //记录累加次数

    }
}
//---------------------------------------------------------------------------
//test.cpp
#include <stdio.h>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

extern unsigned __int64 k;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    k = 0;

    MyThread*t1 = new MyThread(false);
    MyThread*t2 = new MyThread(false);

    t1->WaitFor();
    t2->WaitFor();

    unsigned  int p= t1->count + t2->count;
    char buf[0x20];
    sprintf(buf, "k=%d, p=%d", k, p);

}
//---------------------------------------------------------------------------

这是我根据你的意思也写了个测试代码,希望没有写错。
用上面代码测试,我没发现k大于等于p, 发现在10000000时数据就不等了。

#17


根据你前面描述 把 unsigned __int64 k  改成 unsigned int k也是一样;

#18


呵呵,我用了你的程序在我的环境下(这次是win2000+cb5)结果k和p的值是一样的呀!!!!!!!!!!!!!!p=800000000,呵呵,分毫不差!!!不知道还有哪位仁兄做过这个测试,为什么会相同的程序会有两个结果呢?!!!呵呵

#19


下面你的测试代码是不准的。
qq1->Suspend();
qq2->Suspend();
unsigned int p=qq1->count+qq2->count;  //纪录两个线程所进行的累加次数


计算之前你至少得确认2个线程都暂停了才行,而你没判断,所以我认为你的测试不准。

while(!qq1->Suspended || !qq2->Suspended)
{
   Sleep(300);
   Application->ProcessMessages();
}
unsigned int p=qq1->count+qq2->count;  //纪录两个线程所进行的累加次数

#20


我这次用的可是你的程序呀!!!!!!!!结果还是一样的呀!!!!11

#21


你是说还会出现 k大于等于p ?

#22


k=k+1不是原子操作(即使在单处理器系统中。
不是说不是原子操作就肯定会计算错误,而是说存在这个可能(当某个时候条件符合时)。

#23


我的测试结果是(用你的代码)k==p。一点都不差!!!!!!!!!!!!

#1


不好意思,程序有点点小改动,应该是:
线程类:
 {
class thread1 : public TThread
{
private:
protected:
        void __fastcall Execute();
public:
   unsigned  int count;            //count用来记录累加次数
         __fastcall qin1(bool CreateSuspended);
};
}
 线程程序:
   void __fastcall thread1::Execute()
{while(1)
 {k=k+1;                    //  k为全局变量,所有线程对改变量进行累加,32位数
 this->count=this->count+1;  //记录累加次数
}

主程序:
{
qq1=new thread1(true);
qq1->count=0;
 qq2=new thread1(true);
qq2->count=0;
qq1->Resume();    //开始线程
qq2->Resume();
}

。。。。。       //经过一段时间后,结束线程         
{
qq1->Suspend();
qq2->Suspend();
unsigned int p=qq1->count+qq2->count;  //纪录两个线程所进行的累加次数
。。。。
}

如果按照非原子操作理论,最后的k值应该小于或等于p,但实际上却大于等于p,而且差距小于等于2(属于正常范围),也就是表示:在一个线程的k=k+1中,没有被另外的线程中断。这是为什么呢?!!!!我的环境是:winxp,cb6,32位对齐方式!!!!!

#2


你的测试本身就有问题,
qq1->Suspend(); //线程执行到哪里挂起的? 答案是不确定
qq2->Suspend(); //同样
所以k和p没有可比性.
可以用Terminate停止线程后再比较.

#3


不错,我们不知道线程执行到哪步被挂起!!但是我们可以知道,如果k=k+1没有被中断的话,那么p和k的值不会超过2!!!!!!我们来看看挂起的可能的两个两种情况,要么完成了k=k+1,要么完成了k=k+1和this->count=this->count+1,如果是第一种情况,那么k的值等于count+1,所以说,p和k的值不会超过2(因为有两个线程,而且只有在最后一个线程周期才会被suspend()!!)

#4


mark

#5


k=k+1在现代的编译器中通常编译为Inc [k],可以认为是原子操作。

#6


或者我们变换一下程序:
线程程序:
   void __fastcall thread1::Execute()
{
 unsigned int i=0;
 while(i<400000000)
 {k=k+1;
 i=i+1;
 }
}                   //  k为全局变量,所有线程对改变量进行累加,32位整数
 

当两个线程执行完后,得到的结果是:k=400000000,恰好等于两个线程累加的次数,这难道不足以说明k=k+1这个操作是按照原子操作来完成的么?另外,如果我的全局变量k为64位数(unsigned __int64),那么,得到的结果k与400000000这个值相去廷远的!!!表示对于k=k+1来说,64位数操作不是原子操作!!!而32位数则是一个原子操作!!!但是从汇编的角度来说,全都应该不是原子操作,不知道大家还有没有不同的看法!!!!!???????

#7


不好意思,k的结果应该是800000000,我打错了!!!!!

#8


如果说k=k+1是原子操作,那为什么ms还要特意搞个InterlockedIncrement函数来进行加1原子操作呢,而且还指明只能对32位数使用!!!!!!

#9


InterlockedIncrement的实现不过就是:
lock
inc [address]
使用lock是为了在多处理器系统中独占总线。
仅仅使用inc指令在单处理器系统中是可以算是原子操作的,但是对多处理器系统就存在潜在的问题了。

#10


请问在哪可以看到这方面的资料,我是说有没有权威的证明指出:inc指令在单处理器系统中是可以算是原子操作的,但是对多处理器系统就存在潜在的问题了!!而在多处理器中为何会存在问题,可以具体说说么!!!!!

#11


我觉得不是Inc [k]的原因,因为如果我把程序改为k=k+2;或者k=k+3;这样总不会还编译成Inc [k]了巴,可是结果还是和单线程的结果一样,累加赋值操作并没有被中断!!!!!

#12


而且我把k=k+1改为k=k+s;(s=1)结果也是一样,k=k+s在汇编中不可能一步完成巴!!!!难道不是在c++builder不是在汇编这一层实现多线程的?!!!!!

#13


k=k+s编译后的指令是:
add [k],s  ;s是立即数
仍然是一条指令。

在多处理器系统中存在潜在问题的原因是:
不使用LOCK指令前缀锁定总线的话,在一次内存访问周期中有可能其他处理器会产生异常或中断,而在异常处理中有可能会修改尚未写入的地址,这样当INC操作完成后会产生无效数据(覆盖了前面的修改)。

#14


楼主搞错,k=k+1不是原子操作(即使在单处理器系统中)。而且楼主的推理前提就是有问题的。
不是说不是原子操作就肯定会计算错误,而是说存在这个可能。
另:"如果按照非原子操作理论,最后的k值应该小于或等于p,但实际上却大于等于p",我不知道楼主是怎么测试到k值实际上却大于等于p的。
实际上当操作次数很少时,确实K=K+1确实结果不会错,看起来象是原子操作。

#15


难道没看到我的程序么?实际上我的结果就是k大于等于p,如果是后面那一个程序,实际结果就是累加的次数,一点都没有少,也不多!!!!如果说这样的程序还不能检测出原子操作,那么还有什么办法可以检测出不是原子操作呢?!!!!!

#16


看了一下,感觉你的测试代码这么写是有问题。
//.h
class MyThread : public TThread
{
private:
               
protected:
    void __fastcall Execute();
public:
    __fastcall MyThread(bool CreateSuspended);
    unsigned  int count;//count用来记录累加次数

//.cpp
unsigned __int64 k;//  k为全局变量,所有线程对改变量进行累加

__fastcall MyThread::MyThread(bool CreateSuspended)
    : TThread(CreateSuspended)
{
    count = 0;
}
//---------------------------------------------------------------------------
void __fastcall MyThread::Execute()
{
    //---- Place thread code here ----
    while( count < 400000000 )
    {              
        k = k + 1;
        count = count + 1;  //记录累加次数

    }
}
//---------------------------------------------------------------------------
//test.cpp
#include <stdio.h>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

extern unsigned __int64 k;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    k = 0;

    MyThread*t1 = new MyThread(false);
    MyThread*t2 = new MyThread(false);

    t1->WaitFor();
    t2->WaitFor();

    unsigned  int p= t1->count + t2->count;
    char buf[0x20];
    sprintf(buf, "k=%d, p=%d", k, p);

}
//---------------------------------------------------------------------------

这是我根据你的意思也写了个测试代码,希望没有写错。
用上面代码测试,我没发现k大于等于p, 发现在10000000时数据就不等了。

#17


根据你前面描述 把 unsigned __int64 k  改成 unsigned int k也是一样;

#18


呵呵,我用了你的程序在我的环境下(这次是win2000+cb5)结果k和p的值是一样的呀!!!!!!!!!!!!!!p=800000000,呵呵,分毫不差!!!不知道还有哪位仁兄做过这个测试,为什么会相同的程序会有两个结果呢?!!!呵呵

#19


下面你的测试代码是不准的。
qq1->Suspend();
qq2->Suspend();
unsigned int p=qq1->count+qq2->count;  //纪录两个线程所进行的累加次数


计算之前你至少得确认2个线程都暂停了才行,而你没判断,所以我认为你的测试不准。

while(!qq1->Suspended || !qq2->Suspended)
{
   Sleep(300);
   Application->ProcessMessages();
}
unsigned int p=qq1->count+qq2->count;  //纪录两个线程所进行的累加次数

#20


我这次用的可是你的程序呀!!!!!!!!结果还是一样的呀!!!!11

#21


你是说还会出现 k大于等于p ?

#22


k=k+1不是原子操作(即使在单处理器系统中。
不是说不是原子操作就肯定会计算错误,而是说存在这个可能(当某个时候条件符合时)。

#23


我的测试结果是(用你的代码)k==p。一点都不差!!!!!!!!!!!!