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