Kisser Leon

这个kisser不太冷
posts - 100, comments - 102, trackbacks - 0, articles - 0

多线程计算PI碰到的问题

Posted on 2007-03-23 23:42 kk 阅读(1938) 评论(3)  编辑 收藏 引用 所属分类: IT

例子如下,用于计算 PI 的值。 gIterations 是计算 PI 的迭代次数, gThreadCount 是线程的个数。方法是这样子的,把 PI 分成 gThreadCount 个段,分别让一个线程来执行 PI 的求值操作。求得 PI 值有两种方法,一种是直接把各个线程每一步所求得的值加到 gSum 上去,另一种是把各个线程所求得的值加到一个与之对应的全局变量中去。对每个线程 i ,输出 Thread number:I aaaaaaaa ,表示线程开始执行,输出 Thread number:I bbbbbbb 则表示线程执行完毕。有些地方还可以优化的,不过这里只是为了演示多线程的问题,所以就不予关注了。恩。

代码如下。当只有一个 thread 的时候,结果是 OK 的( gSum==sum==3.14159* ,用等号有点问题,但是结果差异在十万分之一以内)。当有三个 threads 的时候,问题就开始出现了! gSum 计算出来只有 2.* !怎么会这样子呢?各位有兴趣的话,可以运行下面的代码试试看。接着看下面的分析。

#include <windows.h>

#include <stdio.h>

#include <time.h>

 

const int gIterations = 100000000;

const int gThreadCount = 3;

double gSum = 0.0;

double gPart[gThreadCount];

 

DWORD WINAPI threadFunction(LPVOID pArg)

{

    int threadNum = (int)pArg;//starts from 0

    printf("Thread number:%d: aaaaaaaaaaaa\n", threadNum);

    for ( int i=threadNum; i<gIterations; i+=gThreadCount )

    {

        double dx = (i + 0.5f) / gIterations;

        gSum += 4.0f / (1.0f + dx*dx);//cause problems here!

        gPart[threadNum] += 4.0f / (1.0f + dx*dx);

    }

 

    printf("part%d value:%.6f\n", threadNum, gPart[threadNum]/gIterations);

    printf("Thread number:%d: bbbbbbbbbbbb\n", threadNum);

    return 0;

}

 

int main()

{

    memset(gPart, 0.0, sizeof(gPart)/sizeof(double));//init to 0

 

    printf("Computing value of Pi: \n");

    clock_t start = clock();

 

    HANDLE threadHandles[gThreadCount];

    for ( int i=0; i<gThreadCount; i++ )

    {

        threadHandles[i] = CreateThread( NULL,           // Security attributes

                                         0,              // Stack size

                                         threadFunction, // Thread function

                                         (LPVOID)i, // Data for thread func()

                                         0,              // Thread start mode

                                         NULL);          // Returned thread ID

    }

 

    WaitForMultipleObjects(gThreadCount, threadHandles, TRUE, INFINITE);

 

    clock_t finish = clock();

    printf("Executing time:%d\n", finish-start);

 

    printf("global: %f\n", gSum / gIterations);

 

    double sum = 0.0;

    for(int i=0; i<gThreadCount; i++)

        sum += gPart[i];

    printf("parts: %f\n", sum / gIterations);

 

    return 0;

}

 

输出信息:

Computing value of Pi:

Thread number:1: aaaaaaaaaaaa

Thread number:0: aaaaaaaaaaaa

Thread number:2: aaaaaaaaaaaa

part1 value:1.047198

Thread number:1: bbbbbbbbbbbb

part0 value:1.047198

Thread number:0: bbbbbbbbbbbb

part2 value:1.047198

Thread number:2: bbbbbbbbbbbb

Executing time:19109

global: 2.711738

parts: 3.141593

Press any key to continue

以上是输出信息通过 gSum 求出来的值在 2.7 左右,事实上有的时候还会更低。 WHY ?问题出现在哪里呢?通过各个线程计算出来的值是对的,说明问题不是出现在这里,也就是说问题是出现在线程切换的时候使得 gSum 少加了一些值!什么时候切换会导致这个问题呢?问题出现在下面这一句里面:

        gSum += 4.0f / (1.0f + dx*dx);//cause problems here!

这一行等价于:

                   gSum = gSum + value;

这一行代码相当于两行代码:

         temp = gSum + value;

         gSum = temp;

如果有两个线程的话:

线程 A:

1、              temp = gSum + value;

2、              gSum = temp;

线程 B:

3、              temp = gSum + value;

4、              gSum = temp;

由于线程切换的任意性,这几条指令的执行顺序有以下几种可能:

1 2 3 4 1 3 2 4 1 3 4 2 3 1 2 4 3 1 4 2 3 4 1 2

其中 1 3 2 4 顺序就是会出错的,很显然按照 1 3 2 4 顺序的时候 1 中的 value 就没有被加进来了。这就是问题所在!同样 1 3 4 2 3 1 2 4 3 1 4 2 都是有问题。

那如何解决这个问题呢?要把 1 2 捆绑在一起作为一个单位操作,即所谓原子操作,要么不执行,要么就全都执行了。

正确的代码如下。给 gSum+= 操作放到一个 critical section 中,保证此时不会被线程切换干扰。关于 critical section 的详细信息请参考 MSDN Good luck & have fun.

#include <windows.h>

#include <stdio.h>

 

const int gIterations = 100000;

const int gThreadCount = 4;

double gSum = 0.0;

CRITICAL_SECTION gCS;

 

DWORD WINAPI threadFunction(LPVOIDpArg)

{

     double partialSum = 0.0;

 

     for ( inti=(int)pArg+1; i<gIterations; i+=gThreadCount )

     {

         double dx = (i - 0.5f) / gIterations;

         partialSum += 4.0f / (1.0f + dx*dx);

     }

 

     EnterCriticalSection(&gCS);

     gSum += partialSum;

     LeaveCriticalSection(&gCS);

 

     return 0;

}

 

int main ()

{

     printf("Computing value of Pi: \n");

 

     InitializeCriticalSection(&gCS);

     HANDLE threadHandles[gThreadCount];

     for ( inti=0; i<gThreadCount; ++i )

     {

         threadHandles[i] = CreateThread( NULL,           // Security attributes

                                          0,              // Stack size

                                          threadFunction, // Thread function

                                          (LPVOID)i,      // Data for thread func()

                                          0,              // Thread start mode

                                          NULL);          // Returned thread ID

     }

     WaitForMultipleObjects(gThreadCount, threadHandles,  TRUE, INFINITE);

     DeleteCriticalSection(&gCS);

 

     printf("%f\n", gSum / gIterations);

 

     return 0;

}

 

Feedback

# re: 多线程计算PI碰到的问题  回复  更多评论   

2007-03-24 15:05 by 小熊
这应该算是由race condtion产生的问题吧?

# re: 多线程计算PI碰到的问题  回复  更多评论   

2007-03-26 21:14 by 小熊
上面对gSum += 4.0f / (1.0f + dx*dx);//cause problems here!
的分解有误,正确应该由如下这些汇编代码组成:

00401065 fmul qword ptr [dx]
00401068 fadd qword ptr [__real@3ff0000000000000 (403168h)]
0040106E fdivr qword ptr [__real@4010000000000000 (403160h)]
00401074 fadd qword ptr [gSum (4040A8h)]
0040107A fstp qword ptr [gSum (4040A8h)]

而gSum += i;则被翻译成如下这些汇编代码:

00401080 fild dword ptr [i]
00401083 fadd qword ptr [gSum (4040A8h)]
00401089 fstp qword ptr [gSum (4040A8h)]

# re: 多线程计算PI碰到的问题  回复  更多评论   

2007-03-30 15:27 by 小熊
printf("Hello Thread %d\n", num);
这一句被分解为以下汇编代码

00401026 mov esi,esp
00401028 mov ecx,dword ptr [num]
0040102B push ecx
0040102C push offset MSVCR71D_NULL_THUNK_DATA+28h (4030CCh)
00401031 call dword ptr [__imp__printf (40309Ch)]
00401037 add esp,8
0040103A cmp esi,esp
0040103C call _RTC_CheckEsp (4011D0h)

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理