一次面试中,面试官问:进程、线程和线程池了解过么?
我:进程和线程了解过,线程池不太了解。
然后就没有然后了。。。
还是得把基本概念了然于胸才行,下次才能给面试官扯清楚了!
一、进程
进程的概念
进程(process),是指计算机中已运行的程序,是操作系统中分配资源的最小单位。但是进程不是基本的运行单位(线程才是),而是线程的容器。
当我们用 C 语言写一个 hello 程序,编译后得到可执行文件a.out
,在终端输入./a.out
,然后回车,这就等同于我们下达了运行程序的命令,就会产生进程。进程是一个活动的实体,它需要一些资源才能够完成工作,比如CPU使用时间、存储器、文件以及I/O设备等。这里就可以理解前面说的进程就是分配系统资源的最小单位。每个进程都有自己的地址空间,具体看一下进程的存储空间分布情况,下图是32位Inter x86
的Linux中典型的存储空间布局,主要有四部分组成:
- 正文段:存储CPU执行的机器指令
- 数据段
- 初始化的数据
- 未初始化的数据(也被称为
bss
段)
- 堆栈段
- 堆:分配的动态内存就存放在堆区
- 栈:函数调用所需要的信息存放在这一部分
为什么要有进程?
最开始,编程是通过在纸带上打孔,然后将纸带输入计算机进行运行,一个纸带读完了才能读下一个纸带。为了解决这种低效率的排队等候问题,有人发明了批处理系统。将程序(也就是任务)成批地提交给计算机,由计算机自动完成后再输出结果,从而减少作业建立和结束过程中的时间浪费。后来,汇编语言出现,就不再用二进制编码了。但是也有问题产生,当一个程序运行的时候,会一直占用CPU的资源,如果这个程序的大部分时间都在执行IO密集型任务(比如网络IO、磁盘IO等操作,这种任务比较耗时,但CPU消耗不大,因为操作IO的速度远低于CPU和内存的速度),该程序霸占着CPU会造成资源的浪费,此时如果将CPU让给其他的程序使用,等到当前的程序完成数据的接收,再将CPU的资源交还回来,这样就达到了资源的最大利用。但是如何才能解决这个问题呢?
核心也就是如何解决管理和控制不同程序间的计算机资源?解决方案:定义最小的资源分配单位,也就是进程,这也是为什么说进程是分配资源的最小单位。有了进程概念后,每个要运行的程序都分配指定的资源,这样将资源细分化,使得资源被充分地利用,多个程序分时共享硬件和软件资源,因为速度很快,对于我们用户,就感觉多个程序在同时执行。
二、线程
线程的概念
线程(thread)是操作系统能够进行运算调度的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。(摘自wiki)
为什么要有线程呢?
进程能有效提升CPU的利用率。但进程间依然有资源利用优化空间,以及进程间通信的麻烦问题。多线程则可以共享同一进程地址空间上的资源,能在资源的空闲时刻更好的利用,且不存在进程间通信的麻烦。
线程和进程在Linux内核如何实现的?
Linux系统是如何创建进程的呢?对于操作系统,进程就是一个数据结构,我们直接来看 Linux 的源码(include/linux/sched.h 632行 ):
struct task_struct {
// 进程状态
volatile long state;
// 虚拟内存结构体
struct mm_struct *mm;
// 进程号
pid_t pid;
// 指向父进程的指针
struct task_struct __rcu *parent;
// 子进程列表
struct list_head children;
// 存放文件系统信息的指针
struct fs_struct *fs;
// 一个数组,包含该进程打开的文件指针
struct files_struct *files;
};
task_struct
就是 Linux 内核对于一个进程的描述,也可以称为「进程描述符」。mm
指向的是进程的虚拟内存,也就是载入资源和可执行文件的地方;files
指针指向一个数组,这个数组里装着所有该进程打开的文件的指针。每个进程被创建时,files
的前三位被填入默认值,分别指向标准输入流(files[0]
)、标准输出流(files[1]
)、标准错误流(files[2]
)。
首先要明确的是,多进程和多线程都是并发,可以提高处理器的利用效率。我们知道系统调用fork()
可以新建一个进程,函数pthread()
可以创建一个线程。但是进程和线程都是用task_struct
结构体表示的,他们最大的不同就是共享的内存区域不同。
也就是说创建的子进程,会拷贝一份父进程的内存资源(个人感觉这里有点深拷贝的意思);然后创建的子线程,共享父进程的那些内存资源(浅拷贝,父进程和子线程都有指针指向同一块内存区域)。比如mm
和files
都是在线程中共享的。画张图就明白了。
三、线程池
线程池(thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。
任务调度以执行线程的常见方法是使用同步队列,称作任务队列。池中的线程等待队列中的任务,并把执行完的任务放入完成队列中。(摘自wiki)
为什么要有线程池?
因为有些场景需要频繁地创建线程和销毁线程,这样使得系统的压力很大。线程池可以在初始化的时候批量创建线程,然后通过队列等方式提交业务逻辑,线程池中的线程进行逻辑的消费工作,可以在操作的过程中降低线程创建和销毁的开销,但是调度的开销还是存在的。