本篇文章主要对线程与进程之间的区别作一简单总结,从内核实现的区别,双方的地址空间、共享的数据、操作原语的比较和多线程与多进程的区别这几方面,做一简单说明。
1、Linux内核线程实现原理
Unix系统中,早期是没有“线程”概念的,80年代才引入,借助进程机制实现出了线程的概念。因此在这类系统中,进程和线程关系密切。
1. 轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone
2. 从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三级页表是相同的
3. 进程可以蜕变成线程
4. 线程可看做寄存器和栈的集合;每一个线程都有自己的函数调用,所以其拥有自己的用户栈空间;内核区也有一个栈空间用来保存寄存器的值(cpu为不同的线程分配时间片,在不同的线程间切换)
需要拥有自己的内核栈空间。
2、进程与线程的地址空间
对于进程来说,相同的地址(同一个虚拟地址)在不同的进程中,反复使用而不冲突。原因是他们虽虚拟址一样,但,页目录、页表、物理页面各不相同。相同的虚拟址,映射到不同的物理页面内存单元,最终访问不同的物理页面。
但是,线程不同!两个线程具有各自独立的PCB,但共享同一个页目录,也就共享同一个页表和物理页面。所以两个PCB共享一个地址空间。
实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数clone。如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”。
因此:Linux内核是不区分进程和线程的。只在用户层面上进行区分。所以,线程所有操作函数 pthread_* 是库函数,而非系统调用。
3、为什么说在linux下,线程最是小的执行单位;进程是最小的分配资源单位?
当进程调用pthread_create函数后就蜕变成一个线程,经过多次pthread_create后就有了多个线程,而线程和进程的地位是一样的,当cpu在分配时间轮片时会一视同仁。所以说线程是cpu调度的最小单位;而无论在一个进程中创建多少个线程,它们都共享一个0~4G的内存空间,所以说cpu分配资源是以进程为基本单位的。
4、线程默认共享数据段、代码段等地址空间,常用的是全局变量。而进程不共享全局变量,只能借助mmap。
测试:1、进程之间不共享全局变量
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
int a = 10;
int main()
{
pid_t pid ,wpid;
int status;
pid = fork();
if(pid < 0)
{
perror("fork error!\n");
}
else if(pid > 0) //父进程
{
a = 20;
wpid = wait(&status);
if(WIFEXITED(status))
{
printf("The child process %d exit normally\n",wpid);
printf("return value:%d\n",WEXITSTATUS(status));
}
else if(WIFSIGNALED(status))
{
printf("The child process exit abnormally, killed by signal %d\n",WTERMSIG(status));
}
else
{
}
}
else
{
sleep(2);
printf("a = %d\n",a);
}
return 0;
}
运行结果如下:可以看出,父子进程不共享全局变量
➜ test git:(master) ✗ ./a.out
fa process wrote a = 20
a = 10
The child process 11937 exit normally
return value:0
测试线程间共享全局变量,代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
int var = 10;
void *fun(void *agr)
{
var = 20;
printf("pthread!\n");
return NULL;
}
int main()
{
pthread_t tid;
int ret;
int* retval;
printf("before pthread_create: var = %d\n",var);
ret = pthread_create(&tid,NULL,fun,NULL);
if(ret != 0)
{
fprintf(stderr,"pthread_create error : %s\n",strerror(ret));
exit(1);
}
sleep(1);
printf("after pthread: var = %d\n",var);
pthread_join(tid,(void**)&retval);
return 0;
}
运行结果:线程间共享全局变量
before pthread_create: var = 10
pthread!
after pthread: var = 20
5、进程与线程控制原语对比
进程 | 线程 | 作用 |
---|---|---|
fork() | pthread_create() | 创建 |
exit() | pthread_exit() | 退出 |
wait()/waitpid() | pthread_join() | 回收资源 |
kill() | pthread _cancel() | 杀死进程、线程 |
getpid() | pthread_self() | 获取当前进程/线程号 |
6、多线程与多进程的区别
对比维度 | 多进程 | 多线程 | 总结 |
---|---|---|---|
数据共享、同步 | 数据共享复杂,需要IPC;数据是分开的,同步简单 | 因为共享进程数据,数据共享简单,因此,同步复杂 | 各有优势 |
内存、CPU | 占用内存多,切换复杂、CPU利用率低 | 占用内存少,切换简单,CPU利用率高 | 线程占优 |
创建销毁、切换 | 创建销毁、切换复杂,速度慢 | 创建销毁、切换简单,速度快 | 线程占优 |
编程、调试 | 编程简单、调试简单 | 编程复杂、调试复杂 | 进程占优 |
可靠性 | 进程间不会影响 | 一个线程挂掉将会导致整个进程挂掉 | 进程占优 |
分布式 | 适用于多核、多机分布式;扩展机器简单 | 适用于多核分布式 | 进程占优 |