例子如下,用于計算
PI
的值。
gIterations
是計算
PI
的迭代次數(shù),
gThreadCount
是線程的個數(shù)。方法是這樣子的,把
PI
分成
gThreadCount
個段,分別讓一個線程來執(zhí)行
PI
的求值操作。求得
PI
值有兩種方法,一種是直接把各個線程每一步所求得的值加到
gSum
上去,另一種是把各個線程所求得的值加到一個與之對應(yīng)的全局變量中去。對每個線程
i
,輸出
Thread number:I aaaaaaaa
,表示線程開始執(zhí)行,輸出
Thread number:I bbbbbbb
則表示線程執(zhí)行完畢。有些地方還可以優(yōu)化的,不過這里只是為了演示多線程的問題,所以就不予關(guān)注了。恩。
代碼如下。當(dāng)只有一個
thread
的時候,結(jié)果是
OK
的(
gSum==sum==3.14159*
,用等號有點(diǎn)問題,但是結(jié)果差異在十萬分之一以內(nèi))。當(dāng)有三個
threads
的時候,問題就開始出現(xiàn)了!
gSum
計算出來只有
2.*
!怎么會這樣子呢?各位有興趣的話,可以運(yùn)行下面的代碼試試看。接著看下面的分析。
#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
左右,事實(shí)上有的時候還會更低。
WHY
?問題出現(xiàn)在哪里呢?通過各個線程計算出來的值是對的,說明問題不是出現(xiàn)在這里,也就是說問題是出現(xiàn)在線程切換的時候使得
gSum
少加了一些值!什么時候切換會導(dǎo)致這個問題呢?問題出現(xiàn)在下面這一句里面:
??????? gSum += 4.0f / (1.0f + dx*dx);//cause problems here!
這一行等價于:
?????????????????? gSum = gSum + value;
這一行代碼相當(dāng)于兩行代碼:
???????? temp = gSum + value;
???????? gSum = temp;
如果有兩個線程的話:
線程
A:
1、
????????????
temp = gSum + value;
2、
????????????
gSum = temp;
線程
B:
3、
????????????
temp = gSum + value;
4、
????????????
gSum = temp;
由于線程切換的任意性,這幾條指令的執(zhí)行順序有以下幾種可能:
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
就沒有被加進(jìn)來了。這就是問題所在!同樣
1 3 4 2
,
3 1 2 4
,
3 1 4 2
都是有問題。
那如何解決這個問題呢?要把
1
和
2
捆綁在一起作為一個單位操作,即所謂原子操作,要么不執(zhí)行,要么就全都執(zhí)行了。
正確的代碼如下。給
gSum+=
操作放到一個
critical section
中,保證此時不會被線程切換干擾。關(guān)于
critical section
的詳細(xì)信息請參考
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;
}
?