例子如下,用于計(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 ,表示線程開始執(zhí)行,輸出 Thread number:I bbbbbbb 則表示線程執(zhí)行完畢。有些地方還可以優(yōu)化的,不過這里只是為了演示多線程的問題,所以就不予關(guān)注了。恩。
代碼如下。當(dāng)只有一個(gè) thread 的時(shí)候,結(jié)果是 OK 的( gSum==sum==3.14159* ,用等號(hào)有點(diǎn)問題,但是結(jié)果差異在十萬(wàn)分之一以內(nèi))。當(dāng)有三個(gè) threads 的時(shí)候,問題就開始出現(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
以上是輸出信息通過 gSum 求出來(lái)的值在 2.7 左右,事實(shí)上有的時(shí)候還會(huì)更低。 WHY ?問題出現(xiàn)在哪里呢?通過各個(gè)線程計(jì)算出來(lái)的值是對(duì)的,說(shuō)明問題不是出現(xiàn)在這里,也就是說(shuō)問題是出現(xiàn)在線程切換的時(shí)候使得 gSum 少加了一些值!什么時(shí)候切換會(huì)導(dǎo)致這個(gè)問題呢?問題出現(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 就沒有被加進(jìn)來(lái)了。這就是問題所在!同樣 1 3 4 2 , 3 1 2 4 , 3 1 4 2 都是有問題。
那如何解決這個(gè)問題呢?要把 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;
}


