多线程1-进程和线程
进程
在上一篇文章中,我们知道:多任务操作系统通过“并发执行”(并行+并发)能够同时运行多个进程,这些进程能够解决“并发编程”这种问题。
但是在一些特定的情况下(如一些需要频繁创建、销毁的情景),进程并不能很好地解决并发编程问题,这是因为:进程在频繁创建和销毁时花费的系统开销是比较大的。(主要体现在资源的申请和释放上)
早期编写服务器程序时,是使用C语言来进行编写的(基于一种CGI技术,该技术是一种基于多进程的编程模式)。服务器同一时刻会收到很多请求,针对每个请求,都会创造出一个进程,给这个请求进行响应。一个请求处理完之后,这个进程就销毁了。如果请求很多,服务器就需要不停创建新的进程,不停销毁旧的进程,这就会造成较大的系统开销。
由上述我们就知道了进程效率差的原因就在于:资源的申请和释放。我们都知道进程是系统分配资源的基本单位。
当进程启动的时候,就需要为它分配内存资源,操作系统会把该进程所需要的操作和数据以二进制的形式加载到内存中
而系统分配内存的过程是比较繁琐的。需要先指定大小,系统内部把各种大小的空闲内存,通过一定的数据结构,组织起来,再给进程分配过去。
那如果有一个很大的进程,找不到那么大的内存空间这么办呢?
此时系统就会报错,无法启动该线程。(在Windows系统上不明显,在Linux上较为明显)
进程在调度的时候,是“分时复用”的,当前没在运行的进程,可以暂时放在硬盘的特定区域(swap空间),当进程执行的时候,再把这些数据加载到内存中。这样就可以有效地保证,正在运行的进程内存比较充裕。(Linux系统上就不是这样,swap是可以配置的,如果没有进行配置,又运行消耗内存较大的程序,就会报错无法启动)。
线程
那么如何解决进程频繁创建和销毁花费开销较大的问题呢?此时,就要引入线程的概念了。
进程,也被称为“轻量级进程”,是在进程的基础上做了一些改进。对比进程,它保持了独立调度执行,支持“并发执行”,同时也省去了“分配资源”“释放资源”所带来的额外开销。
如果说进程是系统分配资源的基本单位的话,那么线程就是系统调度执行的基本单位。
为什么这么说呢?打个比方,现在我们要完成吃100只烤鸡这样的任务。多进程执行就是分配多个房间和多个人完成这项任务:
而多线程则是直接调度多个人在同一房间完成这项吃烤鸡的任务。
但是,线程也并不是越多越好,从上图我们也可以发现,如果线程太多,就会有人吃不到烤鸡了。
那么线程是如何实现的呢?
下面是多进程和多线程示意图:
可以看到每个进程都是一个单独的PCB(进程控制块),且都单独占据着一块内存空间,而一个进程是包含多个线程的,这些线程共享着一个PCB。
操作系统在进行多任务调度的时候,其本质是在调度PCB(线程中的TCB也有状态 优先级 上下文 记账信息)。
进程和线程在系统中的调度规则是一致的:即随机调度、抢占式执行。
那么什么是随机调度和抢占式执行呢?
1、一个线程,什么时候被调度到CPU上执行,时机是不确定的。
2、一个线程,什么时候从CPU上下来给别人让位,时机也是不确定的。
PCB中有一个属性,内存指针,同样的线程也有,多个线程TCB的内存指针,指向的是同一个内存空间。创建第一个线程是,需要从系统分配资源,后续的线程,就没必要再分配资源了,直接共用前面的资源即可(文件描述符表也是共用的同一份)。
但是,并不是随意的两个进程都能实现资源共享,只是把能够资源共享的线程,分成组,称为“线程组”。每个进程都可以包含一个/多个线程。
线程、进程之间的关系和区别
1、进程是包含线程的。
2、每个线程,是一个独立的执行流,可以执行一些代码,并且单独参与到CPU的调度中。
3、每个进程,有自己的资源,进程中的线程共用这一份资源(内存空间和文件描述符表)。
4、进程是系统资源分配的基本单位,线程是系统调度执行的基本单位。
5、进程和进程之间不会相互影响,但如果同一个进程中的某个线程抛出异常,那么可能会影响到其他线程,会把整个进程中的所有线程都异常终止。
6、线程并不是越多越好,能够合适就好,否则,调度开销会非常明显。
7、同一进程的线程之间,可能会相互干扰,引起线程安全问题