多线程计算PI碰到的问题
来源:互联网 发布:办公室软件培训 编辑:程序博客网 时间:2024/04/30 20:15
例子如下,用于计算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(LPVOID pArg)
{
double partialSum = 0.0;
for ( int i=(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 ( 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);
DeleteCriticalSection(&gCS);
printf("%f/n", gSum / gIterations);
return 0;
}
- 多线程计算PI碰到的问题
- 中值积分定理计算PI值的多线程实现
- Pi的计算程序
- PI的计算公式
- 计算pi的方法
- 并行Pi的计算
- 计算PI的方法
- 多线程计算pi效率对比分析
- 多线程学习中碰到的一个很有意思的问题
- 计算PI值到一亿位的算法
- 计算PI的外星人程序
- 外星人计算Pi的程序
- 计算PI值到一亿位的算法
- 与PI有关的计算
- 外星人计算Pi的程序
- 计算任意位数的Pi
- 计算任意位数的Pi
- 计算任意位数的Pi
- SerialPort In .Net Framework
- 学习Java的30个基本概念
- 《设计模式解析》摘录(11)
- 如何通过COM 接口传送C++对象
- Windows汇编集成开发环境MasmEditor
- 多线程计算PI碰到的问题
- 什么程序员最易找工作
- 如何在.net中导入lib库(2006-07-18)
- vb+mysql编程笔记之二
- 刚到这里,请多指点!我们的发展方向是.NET中的C#。
- 网络报告:美国是黑客大本营 中国是最大受害国
- Fahrenheit 图形API
- 用GPU实现Cellular Texture
- eclipse 快捷键介绍