一位教授曾在课堂上告诉我们,Windows,Linux,OS X和UNIX在线程而不是进程上进行扩展,因此即使在单个处理器上,线程也可能使您的应用程序受益,因为您的应用程序将在CPU上获得更多时间。
我在我的机器上尝试使用以下代码(只有一个CPU)。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_t xs[10];
void *nop(void *ptr) {
unsigned long n = 1UL << 30UL;
while(n--);
return NULL;
}
void test_one() {
size_t len = (sizeof xs) / (sizeof *xs);
while(len--)
if(pthread_create(xs+len, NULL, nop, NULL))
exit(EXIT_FAILURE);
len = (sizeof xs) / (sizeof *xs);
while(len--)
if(pthread_join(xs[len], NULL))
exit(EXIT_FAILURE);
}
void test_two() {
size_t len = (sizeof xs) / (sizeof *xs);
while(len--) nop(NULL);
}
int main(int argc, char *argv[]) {
test_one();
// test_two();
printf("done\n");
return 0;
}
两种测试在速度方面都是相同的。
real 0m49.783s
user 0m48.023s
sys 0m0.224s
real 0m49.792s
user 0m49.275s
sys 0m0.192s
我想,“哇,线程很糟糕”。但是,在具有四个处理器的大学服务器上重复测试接近四倍的速度。
real 0m7.800s
user 0m30.170s
sys 0m0.006s
real 0m30.190s
user 0m30.165s
sys 0m0.004s
在解释家用机器的结果时,我是否忽略了某些内容?
答案 0 :(得分:24)
为了理解任务/线程的内部......让我们看看这个玩具内核代码......
struct regs{ int eax, ebx, ecx, edx, es, ds, gs, fs, cs, ip, flags; struct tss *task_sel; } struct thread{ struct regs *regs; int parent_id; struct thread *next; } struct task{ struct regs *regs; int *phys_mem_begin; int *phys_mem_end; int *filehandles; int priority; int *num_threads; int quantum; int duration; int start_time, end_time; int parent_id; struct thread *task_thread; /* ... */ struct task *next; }
想象一下,内核为该结构task
分配内存,这是一个链表,仔细查看quantum
字段,即基于{{1的处理器时间的时间片。 }}字段。总是会有一个id 0的任务,它永远不会睡觉,只是空闲,可能会发出nops(No OPerationS)...调度程序会在无穷无尽的情况下旋转直到无穷大(即当电源被拔掉时),如果{{1 }}字段确定任务运行20ms,设置priority
和quantum
+ 20ms,当start_time
启动时,内核将cpu寄存器的状态保存为{{1}指针。进入链中的下一个任务,从指针加载cpu寄存器到end_time
并跳转到指令,设置量子和持续时间,当持续时间达到零时,继续下一个...上下文切换...这就是它给出了一个在单个cpu上同时运行的错觉。
现在查看end_time
结构,它是regs
结构中的线程链表。内核为该任务分配线程,为该线程设置cpu状态并跳转到线程......现在内核必须管理线程以及任务本身......再次在任务和线程之间切换上下文...
转到多CPU,内核将被设置为可伸缩,调度程序将执行的操作,将一个regs
加载到一个cpu上,将另一个加载到另一个cpu(双核),并且都跳转到指令指针所指向的位置......现在内核正在两个cpu上同时运行两个任务。扩展到4路,同样的事情,加载到每个CPU上的额外任务,再次向上扩展,到n路......你得到漂移。
正如你所看到的那样,线程不会被视为可扩展的概念,因为内核在跟踪cpu运行的内容方面是一项非常庞大的工作,而且最重要的是,运行哪些线程的任务是什么,从根本上解释了为什么我认为线程不完全可扩展......线程消耗了大量资源......
如果您真的想看看发生了什么,请查看Linux的源代码,特别是在调度程序中。没有坚持,忘记2.6.x内核版本,看看史前版本0.99,调度程序会更容易理解和更容易阅读,当然,它有点旧,但值得一看,这将有助于你理解为什么并希望我的回答也是,为什么线程不可扩展......并展示了玩具操作系统如何使用基于流程的时间划分。我努力不进入现代cpu的技术方面,可以做的更多,然后我所描述的......
希望这有帮助。
答案 1 :(得分:4)
一位教授曾在课堂上告诉我们,Windows,Linux,OS X和UNIX在线程而不是进程上进行扩展,因此即使在单个处理器上,线程也可能使您的应用程序受益,因为您的应用程序将在CPU上获得更多时间。
不一定。如果你的应用程序是唯一运行的CPU密集型产品,那么更多的线程不会神奇地提供更多的CPU时间 - 所有这些都会导致在上下文切换中浪费更多的CPU时间。
我想,“哇,线程很糟糕”。但是,在具有四个处理器的大学服务器上重复测试接近四倍的速度。
这是因为有四个线程,它可以使用所有四个处理器。
答案 2 :(得分:1)
我不确定你究竟在问什么,但这是一个可能有帮助的答案。
在Linux下,进程和线程基本上完全相同。调度程序理解称为“任务”的事情,它并不真正关心它们是否共享地址空间。共享或不共享内容实际上取决于它们的创建方式。
是否使用线程或进程是一个关键的设计决策,不应掉以轻心,但调度程序的性能可能不是一个因素(当然IPC等要求会大大改变设计)