进程
前面我们提到了一个概念是,多任务操作系统,即希望该系统能够同时运行多个程序。本质上说,进程,就算用来解决”并发编程“这样的问题的。
在一些特定的情况下,进程的表现,其实并不能很好的解决”并发编程“,比如,在一些需要频繁的创建和销毁进程的场景时,此时再使用多进程编程,就会造成较大的系统开销(主要体现在资源的申请和释放上)。
早期编写服务器程序的时候,是使用C语言来进行编写的(基于一种CGI技术,该技术是一种基于多进程的编程模式)。服务器同一时刻就会收到很多请求,针对每个请求,都会创造出一个进程,给这个请求提供一定的服务,返回对应的相应。一旦这个请求处理完之后,这个进程就要被销毁了。如果请求很多,就意味着,服务器需要不停的创建新的进程,也需要不停的销毁旧的进程。这样频繁的进行创建和销毁操作,造成的系统开销是比较大的!!!、
关键原因:资源的申请和释放。前面我们提到,进程是资源(CPU,硬盘,内存,网络带宽...)分配的基本单位。
一个进程,在刚刚启动的时候,首当其冲,会把内存资源。进程需要把依赖的代码和数据,加载到内存资源中。
从系统中分配一个内存的过程是比较繁琐的。一般来说,先需要指定一个大小,系统内部把各种大小的空闲内存,通过一定的数据结构,给组织起来,然后再给进程分配过去。
如果有一个很大的进程,找不到那么大的内存空间怎么办呢?
答:系统报错,无法启动。(在Windows系统上并不明显,在Linux上较为明显)
进程在调度的时候,是”分时复用“的,进程消耗的内存,在内存空间上,也是”分时复用“的。当前没在运行的进程,所消耗的内存空间,可以暂时不必真正放在”内存上“,可以暂时放在硬盘的特定区域(swap 空间),当进程真正执行的时候,再把这些内存数据装载进去。这样就可以有效的保证,正在运行的进程内存比较充裕。(Linux系统上就不是了,swap是可以配置的,如果没有进行配置,并且运行消耗内存比较大的程序,就容易报错无法启动)
线程
如何解决上述问题呢? --> 线程
线程,也可以称之为”轻量级进程“,是在进程的基础上,做了一些改进。对比进程,他保持了独立调度执行,支持”并行开发“,同时也省去了”分配资源“ ”释放资源“所带来的额外开销。
线程是如何实现的呢?
前面我们使用PCB来描述一个进程:
在上图中,是一个进程有一个PCB,但是实际上,一个进程是可以有多个PCB的,即,一个进程可以包含多个线程。
操作系统在进行”多任务调度“的时候,其本质其实是在调度PCB(线程在系统中的调度规则,和进程是一样的,线程中的PCB也有状态 优先级 上下文 记账信息)
PCB中有一个属性,是内存指针,多个线程的PCB的内存指针,指向的是同一个内存空间。这就意味着:只是创建第一个线程的时候,需要从系统分配资源,后续的线程,就不必再分配了,直接共用前面的资源即可。(除了内存之外,文件描述操作符也是多个线程共用一份的)
补充:也不是随意两个线程,都可以实现资源共享,只是把能够资源共享的线程,分成组,称为”线程组“。每个进程,都可以包含一个 / 多个线程。
理解进程和线程的关系:
在引出线程前,进程需要扮演两个角色-->1.资源分配的基本单位,调度执行的基本单位
有了线程之后,进程专注于-->资源分配的基本单位,线程负责-->调度执行。
创建进程,资源就分配了,只不过,一个进程中,至少要包含一个线程。
总结:
1.进程是包含线程的。
2.每个线程,都是一个独立的执行流,可以执行一些代码,并且单独的参与到CPU的调度中(状态,上下文,优先级,记账信息...每个线程都是自己的独立的一份)
3.每个进程,也有自己的资源,并且与其中的线程共用内存空间和文件描述符表。
==》 进程是资源分配的资本单位,线程的调度执行的基本单位。
4.进程和进程之间,并不会相互影响。
5.同一个进程的线程之间,可能会相互干扰 --> 引起线程安全问题
6.线程并不是越多越好,合理适当才重要。(如果线程太多了,那调度开销可能会剧增)
完