例子如下,用于计算
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;
}