进程与线程、协程的区别

一、进程与线程

进程是资源分配的最小单位,线程是CPU调度的最小单位

做个简单的比喻:进程=火车,线程=车厢线程在进程下行进(单纯的车厢无法运行)

  • 一个进程可以包含多个线程(一辆火车可以有多个车厢)
  • 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
  • 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
  • 进程间不会相互影响,一个线程挂掉可能将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢。而JAVA中不是,线程挂掉不会让进程挂掉)
  • 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
  • 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-“互斥锁”
  • 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”

二、线程与协程

为什么需要协程?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我们都知道多线程,当需要同时执行多项任务的时候,就会采用多线程并发执行。拿手机支付举例子,当收到付款信息的时候,需要查询数据库来判断余额是否充足,然后再进行付款。

假设最开始我们只有可怜的10个用户,收到10条付款消息之后,我们开启启动10个线程去查询数据库,由于用户量很少,结果马上就返回了。第2天用户增加到了100人,你选择增加100个线程去查询数据库,等到第三天,你们加大了优惠力度,这时候有1000人同时在线付款,你按照之前的方法,继续采用1000个线程去查询数据库,并且隐隐觉察到有什么不对。

不断增长的线程
几天之后,见势头大好,运营部门开始不停的补贴消费券,展开了史无前例的大促销,你们的用户开始爆炸增长,这时候有10000人同时在线付款,你打算启动10000个线程来处理任务。等等,问题来了,因为每个线程至少会占用4M的内存空间,10000个线程会消耗39G的内存,而服务器的内存配置只有区区8G,这时候你有2种选择,一是选择增加服务器,二是选择提高代码效率。那么是否有方法能够提高效率呢?

我们知道操作系统在线程等待IO的时候,会阻塞当前线程,切换到其它线程,这样在当前线程等待IO的过程中,其它线程可以继续执行。当系统线程较少的时候没有什么问题,但是当线程数量非常多的时候,却产生了问题。一是系统线程会占用非常多的内存空间,二是过多的线程切换会占用大量的系统时间。

线程切换
协程刚好可以解决上述2个问题。协程运行在线程之上,当一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程,而且协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多。

协程切换
回到上面的问题,我们只需要启动100个线程,每个线程上运行100个协程,这样不仅减少了线程切换开销,而且还能够同时处理10000个读取数据库的任务,很好的解决了上述任务。

知道了协程的工作方式,那么我们再看下使用协程有哪些注意事项。

协程的注意事项

实际上协程并不是什么银弹,协程只有在等待IO的过程中才能重复利用线程,上面我们已经讲过了,线程在等待IO的过程中会陷入阻塞状态,意识到问题没有?

假设协程运行在线程之上,并且协程调用了一个阻塞IO操作,这时候会发生什么?实际上操作系统并不知道协程的存在,它只知道线程,因此在协程调用阻塞IO操作的时候,操作系统会让线程进入阻塞状态,当前的协程和其它绑定在该线程之上的协程都会陷入阻塞而得不到调度,这往往是不能接受的。

因此在协程中不能调用导致线程阻塞的操作。

简单来说

线程有最小的内存单位,而且线程的切换由系统进行,由于系统切换调度需要传入上下文,当线程一多的时候,切换来切换去,所以开销大(因为切换和保存恢复上下文)。但是很多场景,上下文可以我们自己搞,系统只会机械的调换,所以能避免一些消耗。而且线程需要系统态,而协程是用户态的,占用小。

区别和实现

进程、线程,都是有内核进行调度,有 CPU 时间片的概念,进行 抢占式调度。

协程(用户级线程)完全由用户自己的程序进行调度(协作式调度),需要协程自己主动把控制权转让出去之后,其他协程才能被执行到。

goroutine 和协程区别

本质上,goroutine 就是协程。 不同的是,Golang 在 runtime、系统调用等多方面对 goroutine 调度进行了封装和处理,当遇到长时间执行或者进行系统调用时,会主动把当前 goroutine 的CPU § 转让出去,让其他 goroutine 能被调度并执行,也就是 Golang 从语言层面支持了协程。Golang 的一大特色就是从语言层面原生支持协程,在函数或者方法前面加 go关键字就可创建一个协程。

线程是操作系统的内核对象,多线程编程时,如果线程数过多,就会导致频繁的上下文切换,这些 cpu时间是一个额外的耗费。

协程,是在应用层模拟的线程,他避免了上下文切换的额外耗费,兼顾了多线程的优点。简化了高并发程序的复杂度。

协程如何实现的?

协程是基于线程的。内部实现上,维护了一组数据结构和 n 个线程,真正的执行还是线程,协程执行的代码被扔进一个待执行队列中,由这 n 个线程从队列中拉出来执行。这就解决了协程的执行问题。那么协程是怎么切换的呢?答案是:golang 对各种 io函数 进行了封装,这些封装的函数提供给应用程序使用,而其内部调用了操作系统的异步 io函数,当这些异步函数返回 busy 或 bloking 时,golang 利用这个时机将现有的执行序列压栈,让线程去拉另外一个协程的代码来执行,基本原理就是这样,利用并封装了操作系统的异步函数。包括 linux 的 epoll、select 和 windows 的 iocp、event 等。

在任务调度上,协程是弱于线程的。但是在资源消耗上,协程则是极低的。一个线程的内存在 MB 级别,而协程只需要 KB 级别。而且线程的调度需要内核态与用户的频繁切入切出,资源消耗也不小。

五大状态

在这里插入图片描述

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值