首页 > 解决方案 > 在 C 中生成具有相同参数的 n 个 pthread 的最有效方法

问题描述

我有 32 个线程,我提前知道输入参数,函数内部没有任何变化(除了每个线程与之交互的内存缓冲区)。

在伪 C 代码中,这是我的设计模式:

// declare 32 pthreads as global variables

void dispatch_32_threads() {
   for(int i=0; i < 32; i++) {
      pthread_create( &thread_id[i], NULL, thread_function, (void*) thread_params[i] );
   }
   // wait until all 32 threads are finished
   for(int j=0; j < 32; j++) {
      pthread_join( thread_id[j], NULL); 
   }
}

int main (crap) {

    //init 32 pthreads here

    for(int n = 0; n<4000; n++) {
        for(int x = 0; x<100< x++) {
            for(int y = 0; y<100< y++) {
                dispatch_32_threads();
                //modify buffers here
            }
        }
    }
}

我在呼唤dispatch_32_threads 100*100*4000= 40000000时代。thread_function并且(void*) thread_params[i]不要改变。我认为pthread_create不断创建和销毁线程,我有 32 个内核,它们都没有达到 100% 的利用率,它徘徊在 12% 左右。此外,当我将线程数减少到 10 个时,所有 32 个内核的利用率都保持在 5-7%,而且我看不到运行时的速度下降。运行少于 10 次会减慢速度。

然而,运行 1 个线程非常慢,因此多线程很有帮助。我分析了我的代码,我知道它thread_func很慢,并且thread_func是可并行的。这让我相信,pthread_create在不同的内核上不断产生和销毁线程,在 10 个线程之后我会失去效率,而且速度会变慢,thread_func本质上比产生 10 多个线程“简单”。

这个评价是真的吗?100% 利用所有内核的最佳方法是什么?

标签: multithreadingg++pthreadspthread-join

解决方案


线程创建是昂贵的。它取决于不同的参数,但很少低于 1000 个循环。和线程同步和销毁类似。如果您的 thread_function 中的工作量不是很高,它将在很大程度上支配计算时间。

在内部循环中创建线程很少是一个好主意。也许,最好的办法是创建线程来处理外循环的迭代。根据您的程序以及thread_function迭代之间可能存在依赖关系,这可能需要一些重写,但解决方案可能是:

int outer=4000;
int nthreads=32;
int perthread=outer/nthreads;

// add an integer with thread_id to thread_param struct
void thread_func(whatisrequired *thread_params){
  // runs perthread iteration of the loop beginning at start
    int start = thread_param->thread_id;
    for(int n = start; n<start+perthread; n++) {
        for(int x = 0; x<100< x++) {
            for(int y = 0; y<100< y++) {
                //do the work
            }
        }
    }
}

int main(){
   for(int i=0; i < 32; i++) {
      thread_params[i]->thread_id=i;
      pthread_create( &thread_id[i], NULL, thread_func, 
              (void*) thread_params[i]);
   }
   // wait until all 32 threads are finished
   for(int j=0; j < 32; j++) {
      pthread_join( thread_id[j], NULL); 
   }
}

通过这种并行化,您可以考虑使用 openmp。该parallel for子句将使您轻松尝试最佳并行化方案。

如果存在依赖关系并且无法实现如此明显的并行化,您可以在程序启动时创建线程并通过管理线程池让它们工作。管理队列比创建线程成本低(但原子访问确实有成本)。

编辑:或者,您可以
1. 将所有循环放入线程函数
2. 在内部循环的开头(或结尾)添加一个屏障以同步您的线程。这将确保所有线程都完成了它们的工作。
3.在main创建所有线程并等待完成。
屏障比线程创建成本更低,结果将是相同的。


推荐阅读