为啥多线程(使用 pthread)似乎比多进程(使用 fork)慢?
Posted
技术标签:
【中文标题】为啥多线程(使用 pthread)似乎比多进程(使用 fork)慢?【英文标题】:Why the multi-threading(using pthread) seems slower than multi-process(using fork)?为什么多线程(使用 pthread)似乎比多进程(使用 fork)慢? 【发布时间】:2021-12-14 01:49:18 【问题描述】:这里我尝试使用 3 种方法将 0 到 1e9 之间的所有数字相加:
-
正常顺序执行(单线程)
创建多个进程以添加较小的部分(使用 fork)并在最后添加所有较小的部分,并且
创建多个线程以执行与第二种方法相同的操作。
据我所知,线程创建速度很快,因此被称为轻量级进程。
但是在执行我的代码时,我发现第二种方法(多进程)最快,其次是第一种方法(顺序),然后是第三种方法(多线程)。但我无法弄清楚为什么会发生这种情况(可能是执行时间计算中的一些错误,或者是我的系统中有一些不同的东西等等)。
这是我的代码 C 代码:
#include "stdlib.h"
#include "stdio.h"
#include "unistd.h"
#include "string.h"
#include "time.h"
#include "sys/wait.h"
#include "sys/types.h"
#include "sys/sysinfo.h"
#include "pthread.h"
#define min(a,b) (a < b ? a : b)
int n = 1e9 + 24; // 2, 4, 8 multiple
double show(clock_t s, clock_t e, int n, char *label)
double t = (double)(e - s)/(double)(CLOCKS_PER_SEC);
printf("=== N %d\tT %.6lf\tlabel\t%s === \n", n, t, label);
return t;
void init()
clock_t start, end;
long long int sum = 0;
start = clock();
for(int i=0; i<n; i++) sum += i;
end = clock();
show(start, end, n, "Single thread");
printf("Sum %lld\n", sum);
long long eachPart(int a, int b)
long long s = 0;
for(int i=a; i<b; i++) s += i;
return s;
// multiple process with fork
void splitter(int a, int b, int fd[2], int n_cores) // a,b are useless (ignore)
clock_t s, e;
s = clock();
int ncores = n_cores;
// printf("cores %d\n", ncores);
int each = (b - a)/ncores, cc = 0;
pid_t ff;
for(int i=0; i<n; i+=each)
if((ff = fork()) == 0 )
long long sum = eachPart(i, min(i + each, n) );
// printf("%d->%d, %d - %d - %lld\n", i, i+each, cc, getpid(), sum);
write(fd[1], &sum, sizeof(sum));
exit(0);
else if(ff > 0) cc++;
else printf("fork error\n");
int j = 0;
while(j < cc)
int res = wait(NULL);
// printf("finished r: %d\n", res);
j++;
long long ans = 0, temp;
while(cc--)
read(fd[0], &temp, sizeof(temp));
// printf("c : %d, t : %lld\n", cc, temp);
ans += temp;
e = clock();
show(s, e, n, "Multiple processess used");
printf("Sum %lld\tcores used %d\n", ans, ncores);
// multi threading used
typedef struct SS
int s, e;
SS;
int tfd[2];
void* subTask(void *p)
SS *t = (SS*)p;
long long *s = (long long*)malloc(sizeof(long long));
*s = 0;
for(int i=t->s; i<t->e; i++)
(*s) = (*s) + i;
write(tfd[1], s, sizeof(long long));
return NULL;
void threadSplitter(int a, int b, int n_thread) // a,b are useless (ignore)
clock_t sc, e;
sc = clock();
int nthread = n_thread;
pthread_t thread[nthread];
int each = n/nthread, cc = 0, s = 0;
for(int i=0; i<nthread; i++)
if(i == nthread - 1)
SS *t = (SS*)malloc(sizeof(SS));
t->s = s, t->e = n; // start and end point
if((pthread_create(&thread[i], NULL, &subTask, t))) printf("Thread failed\n");
s = n; // update start point
else
SS *t = (SS*)malloc(sizeof(SS));
t->s = s, t->e = s + each; // start and end point
if((pthread_create(&thread[i], NULL, &subTask, t))) printf("Thread failed\n");
s += each; // update start point
long long ans = 0, tmp;
// for(int i=0; i<nthread; i++)
// void *dd;
// pthread_join(thread[i], &dd);
// // printf("i : %d s : %lld\n", i, *((long long*)dd));
// ans += *((long long*)dd);
//
int cnt = 0;
while(cnt < nthread)
read(tfd[0], &tmp, sizeof(tmp));
ans += tmp;
cnt += 1;
e = clock();
show(sc, e, n, "Multi Threading");
printf("Sum %lld\tThreads used %d\n", ans, nthread);
int main(int argc, char* argv[])
init();
printf("argc : %d\n", argc);
// ncore - processes
int fds[2];
pipe(fds);
int cores = get_nprocs();
splitter(0, n, fds, cores);
for(int i=1; i<argc; i++)
cores = atoi(argv[i]);
splitter(0, n, fds, cores);
// nthread - calc
pipe(tfd);
threadSplitter(0, n, 16);
for(int i=1; i<argc; i++)
int threads = atoi(argv[i]);
threadSplitter(0, n, threads);
return 0;
输出结果:
=== N 1000000024 T 2.115850 label Single thread ===
Sum 500000023500000276
argc : 4
=== N 1000000024 T 0.000467 label Multiple processess used ===
Sum 500000023500000276 cores used 8
=== N 1000000024 T 0.000167 label Multiple processess used ===
Sum 500000023500000276 cores used 2
=== N 1000000024 T 0.000436 label Multiple processess used ===
Sum 500000023500000276 cores used 4
=== N 1000000024 T 0.000755 label Multiple processess used ===
Sum 500000023500000276 cores used 6
=== N 1000000024 T 2.677858 label Multi Threading ===
Sum 500000023500000276 Threads used 16
=== N 1000000024 T 2.204447 label Multi Threading ===
Sum 500000023500000276 Threads used 2
=== N 1000000024 T 2.235777 label Multi Threading ===
Sum 500000023500000276 Threads used 4
=== N 1000000024 T 2.534276 label Multi Threading ===
Sum 500000023500000276 Threads used 6
另外,我使用管道来传输子任务的结果。在多线程中,我也尝试使用连接线程并顺序合并结果,但最终结果在 2 秒左右的执行时间左右。
输出:
【问题讨论】:
AFAIK,clock
不要衡量你认为它做了什么(即不是挂钟时间)。 Il 不会以相同的方式处理线程和进程。尝试使用另一种方式来测量时间,例如gettimeofday
。
clock() 可能会赢得标准 C 库中最不幸命名函数的奖项:/
【参考方案1】:
TL;DR:您以错误的方式测量时间。使用clock_gettime(CLOCK_MONOTONIC, ...)
而不是clock()
。
您正在使用clock()
测量时间,如手册页所述:
[...] 返回程序使用的处理器时间的近似值。 [...] 返回的值是到目前为止使用的 CPU 时间,
clock_t
clock()
使用的系统时钟测量 CPU 时间,即调用进程在使用 CPU 时所花费的时间。进程使用的 CPU 时间是其所有线程使用的 CPU 时间的总和,但不是它的子线程,因为它们是不同的进程。另见:What specifically are wall-clock-time, user-cpu-time, and system-cpu-time in UNIX?
因此,在您的 3 个场景中会发生以下情况:
没有并行性,顺序代码。运行进程所花费的 CPU 时间几乎是所有需要衡量的,并且与实际花费的挂钟时间非常相似。请注意,单线程程序的 CPU 时间总是小于或等于其挂钟时间。
多个子进程。由于您正在创建子进程来代表主(父)进程执行实际工作,因此父进程将使用几乎为零的 CPU 时间:它唯一要做的就是创建子进程的一些系统调用,然后是一些系统调用等待它们退出。它的大部分时间都花在等待孩子的睡眠上,而不是在 CPU 上运行。子进程是在 CPU 上运行的进程,但您根本没有测量它们的时间。因此,您最终会得到很短的时间(1ms)。你基本上没有在这里测量任何东西。
多线程。由于您正在创建 N 个线程来完成工作,并且仅在主线程中占用 CPU 时间,因此您的进程的 CPU 时间将占线程 CPU 时间的总和。毫不奇怪,如果您进行完全相同的计算,每个线程花费的平均 CPU 时间为 T/NTHREADS,将它们相加将得出 T/NTHREADS * NTHREADS = T。事实上,您使用的大致是与第一种情况相同的 CPU 时间,只是创建和管理线程的开销很小。
所有这些都可以通过两种方式解决:
-
在每个线程/进程中以正确的方式仔细计算 CPU 时间,然后根据需要对这些值进行求和或平均。
使用
clock_gettime
和CLOCK_REALTIME
、CLOCK_MONOTONIC
或CLOCK_MONOTONIC_RAW
之一简单地测量挂钟时间(即真实人类时间)而不是CPU 时间。请参阅the manual page 了解更多信息。
【讨论】:
非常感谢。现在,结果相当合理,但对于较大的 n 值,多进程方法仍然稍微好一些(大约 0.1 秒)。有什么理由吗?或者是因为多个过程,准确的时间计算很困难。 @devi_D 我不确定可能是什么问题,如果我用clock_gettime(CLOCK_REALTIME, ...)
替换代码中的clock()
调用并调整show
函数以获取两个struct timespect
和正确打印时间我可以看到多个子线程比多个线程快 0.1 秒,这又比单线程快。如果线程的运行速度比多子场景慢,您可能在线程场景中开销过多(例如不需要管道)。
@devi_D 如果我使用-O3
编译,我可以看到多线程方案是最快的,因此可能是编译器在较低的优化级别上没有足够好地优化内存访问。 以上是关于为啥多线程(使用 pthread)似乎比多进程(使用 fork)慢?的主要内容,如果未能解决你的问题,请参考以下文章
Python36 1.joinablequeue 2.线程理论 3.多线程对比多进程 4.线程的使用方式 4.1.产生 线程的两种方式 4.2.守护线程 4.3.线程安全问题 4.3.1.互斥锁 4
从 pthread 调用 sleep() 是不是会使线程进入睡眠状态或进程?
Linux_多线程(进程与线程的联系_pthread库_线程创建_线程等待_线程正常终止_线程取消_线程分离_pthread_t与LWP)