重复使用已创建的线程以减少系统的额外开销

在多核平台上开发程序,我们主张把子任务并行化。这样需要创建多个进程。问题是,是不是线程越多越好呢?

下面是把多任务进行划分,并行工作的例子。第一种方法共创建了84个子线程;第二种方法仅创建了4个子线程(我的实验平台是四核的CPU)

限于篇幅的限制,和便于对照。说明如下:1)紫色代码是在原代码上新增的;2)蓝色的代码是修改对应的函数(原代码已不再用);3)黑色的是原代码(含被注释掉的)

这样,也可以很方便的编译原代码,以作比较。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <windows.h>
 
#define NPARTS 1000
#define NITER 21
#define DIMS 3
#define NUM_THREADS 4
 
//int rand( void );
DWORD WINAPI computePot(LPVOID);
void initPositions(void);
void updatePositions(void);
 
double r[DIMS][NPARTS];
int bounds[2][NUM_THREADS];
double pot;
double gPot[NUM_THREADS];

DWORD WINAPI tPoolComputePot(LPVOID); // 增加线程池的控制程序 
int done = 0;
HANDLE bSignal[NUM_THREADS]; // 信号量用作计算开始
HANDLE eSignal[NUM_THREADS]; // 信号量用作计算结束


// 增加新的计算控制函数
DWORD WINAPI tPoolComputePot(LPVOID pArg) {
  int *tid;
  tid = (int *)pArg;


  while (!done) {
  WaitForSingleObject(bSignal[*tid], INFINITE); //等待开始信号
  computePot(tid);
  SetEvent(eSignal[*tid]); //发出结束信号
  }
  return 0;
}


/*
int main() {
  int i, j;
  HANDLE tHandle[NUM_THREADS];
  int tNum[NUM_THREADS];

  for (i=0; i<NUM_THREADS; i++) {
  bounds[0][i] = i * (NPARTS/NUM_THREADS);
  bounds[1][i] = (i+1) * (NPARTS/NUM_THREADS);
  }
  bounds[1][NUM_THREADS-1] = NPARTS;

  initPositions();
  updatePositions();
 
  for( i=0; i<NITER; i++ ) {
  pot = 0.0;
  for (j=0; j<NUM_THREADS; j++) {
  tNum[j] = j;
  tHandle[j] = CreateThread(NULL, 0, computePot, &tNum[j], 0, NULL);
  }
  WaitForMultipleObjects(NUM_THREADS, tHandle, TRUE, INFINITE);

  for (j=0; j<NUM_THREADS; j++) {
  pot += gPot[j];
  }
  if (i%10 == 0) printf("%5d: Potential: %10.3f\n", i, pot);
  updatePositions();
  }
}
*/

int main() {
  int i, j;
  HANDLE tHandle[NUM_THREADS];
  int tNum[NUM_THREADS];


  for (i=0; i<NUM_THREADS; i++) {
  bounds[0][i] = i * (NPARTS/NUM_THREADS);
  bounds[1][i] = (i+1) * (NPARTS/NUM_THREADS);
  bSignal[i] = CreateEvent(NULL, FALSE, FALSE, NULL); // auto-reset
  eSignal[i] = CreateEvent(NULL, FALSE, FALSE, NULL); // auto-reset
  }
  bounds[1][NUM_THREADS-1] = NPARTS;


  for (j=0; j<NUM_THREADS; j++) {
  tNum[j] = j;
  tHandle[j] = CreateThread(NULL, 0, tPoolComputePot, &tNum[j], 0, NULL);
  }


  initPositions();
  updatePositions();
 
  for( i=0; i<NITER; i++ ) {
 WaitForMultipleObjects(NUM_THREADS, eSignal, TRUE, INFINITE); //上次已结束?


 pot = 0.0;
 for (j=0; j<NUM_THREADS; j++) {
 pot += gPot[j];
 }
 if (i%10 == 0) printf("%5d: Potential: %10.3f\n", i, pot);
 updatePositions();
  }
  done = 1; //全部处理完
  for (j=0; j<NUM_THREADS; j++)
  SetEvent(bSignal[i]);
   
  WaitForMultipleObjects(NUM_THREADS, tHandle, TRUE, INFINITE);
}
 
void initPositions() {
  int i, j;
 
  for( i=0; i<DIMS; i++ )
  for( j=0; j<NPARTS; j++ ) 
  r[i][j] = 0.5 + ( (double) rand() / (double) RAND_MAX );
}
 
/*
void updatePositions() {
  int i, j;
 
  for( i=0; i<DIMS; i++ )
  for( j=0; j<NPARTS; j++ )
  r[i][j] -= 0.5 + ( (double) rand() / (double) RAND_MAX );
}
*/

// 修改updatePositions 函数
void updatePositions() {
  // 保持原来工作,增加下面内容-发出”可以”工作信号
  int j;
  for (j=0; j<NUM_THREADS; j++)
  SetEvent(bSignal[j]);
}


 
DWORD WINAPI computePot(LPVOID pArg) {
  int i, j, start, end, *tid;
  double lPot = 0.0;
  double distx, disty, distz, dist;

  tid = (int *) pArg;
  start = bounds[0][*tid];
  end = bounds[1][*tid];
 
  for( i=start; i<end; i++ ) {
  for( j=0; j<i-1; j++ ) {
  distx = pow( (r[0][j] - r[0][i]), 2 );
  disty = pow( (r[1][j] - r[1][i]), 2 );
  distz = pow( (r[2][j] - r[2][i]), 2 );
  dist = sqrt( distx + disty + distz );
  lPot += 1.0 / dist;
  }
  }
  gPot[*tid] = lPot;
  return 0;
}


可以看到第一种方法虽然创建了84个子线程,可是子任务都很短,并行的效果并不理想;第二种方法是反复利用已创建的4个子线程来完成所有的短任务,并行效果非常理想。

下面是用Intel® Thread Profiler 得到的二个结果:第一种方法运行了近6秒,而第二种方法只运行了3秒多。





所以当你要创建大量的线程时,要考虑是否必要!

Reportez-vous à notre Notice d'optimisation pour plus d'informations sur les choix et l'optimisation des performances dans les produits logiciels Intel.