当前位置:   article > 正文

每日一题:进程 线程 协程区别_每个线程都有自己独立的地址空间吗

每个线程都有自己独立的地址空间吗

得分点 地址空间开销并发性内存

标准回答:

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。

  1. 进程有独立的地址空间,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间;
  2. 进程和线程切换时,需要切换进程和线程的上下文,进程的上下文切换时间开销远远大于线程上下文切换时间,耗费资源较大,效率要差一些;
  3. 进程的并发性较低,线程的并发性较高;
  4. 每个独立的进程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制;
  5. 系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了 CPU 外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源;
  6. 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
  7. 协程上下文开销较小,不需要进行 用户态-系统态-用户态的切换,只需在用户态切换即可。
  8. 可以让多个协程运行在一个线程之上,所以并不会增加线程的数量,多线程开发中线程所占内存空间不会剧增。并且这些协程都是可复用的,线程可以采用时分复用的方法使用这些协程。

1. 独立的地址空间:

在这里插入图片描述

2.上下文切换开销:

进程作为资源分配和调度的基本单位,频繁的对进程进行调度和切换,这种上下文切换的开销无疑是巨大的,而线程只是资源调度的基本单位,与进程相比上下文切换的开销减小了很多。如下图,创建、撤销进程以及上下文切换中开销较大。
在这里插入图片描述

进程PCB中有的信息:
在这里插入图片描述
在这里插入图片描述
线程TCB所需信息:
在这里插入图片描述
在这里插入图片描述

内核态与用户态:
在这里插入图片描述
在这里插入图片描述

3.并发性问题:

进程可以并发执行,进程之间的线程也可以并发执行因此大大提高了并发性。

4. 内存资源问题

系统不会为线程分配内存资源,一个进程有多个线程,多个线程共享系统给进程提供的资源,线程本身指存在少量自身运行的一些必要资源。
在这里插入图片描述

二、协程

为什么会有上下文的切换?
在任何一个时刻,单处理器系统都只能执行一个进程的代码。当操作系统决定要把控制权从当前进程转移到某个新进程时,就会进行上下文切换,即保存当前进程的上下文、恢复新进程的上下文,然后将控制权传递到新进程。新进程就会从它上次停止的地方开始。

上下文切换为什么要陷入内核?
假设现在有两个并发的进程:shel进程和hello进程。最开始,只有 shell进程在运行,即等待命令行上的输人。当我们让它运行hello程序时, shell通过调用一个专门的函数,即系统调用,来执行我们的请求,系统调用会将控制权传递给操作系统。操作系统保存 shell进程的上下文,创建一个新的hello进程及其上下文,然后将控制权传给新的hello进程。hello进程终止后,操作系统恢复shll进程的上下文,并将控制权传回给它, shell进程会继续等待下一个命令行输入。

在这里插入图片描述
从上面这个实例我们可以得出结论:

(1)上一个进程的上下文信息还在内存和处理器当中,我们要保存这些信息的话,就必须陷入到内核态才可以。

(2)创建一个新的进程,以及它的上下文信息,并且将控制权交给这个新进程,这些都只有在内核态才能实现。

综上,我们可以得出结论,进程和线程的上下文切换相较于协程比较“耗时耗力”。

那么协程的上下文切换相较线程有哪些提升?
协程上下文切换只涉及CPU上下文切换,而所谓的CPU上下文切换是指少量寄存器(PC / SP / DX)的值修改,协程切换非常简单,就是把当前协程的 CPU 寄存器状态保存起来,然后将需要切换进来的协程的 CPU 寄存器状态加载的 CPU 寄存器上就 ok 了。而对比线程的上下文切换则需要涉及模式切换(从用户态切换到内核态)、以及 16 个寄存器、PC、SP…等寄存器的刷新;
从多线程角度来讲:
很明显,CPU 调度切换的是进程和线程。尽管线程看起来很美好,但实际上多线程开发设计会变得更加复杂,要考虑很多同步竞争等问题,如锁、竞争冲突等。

多进程、多线程已经提高了系统的并发能力,但是在当今互联网高并发场景下,为每个任务都创建一个线程是不现实的,因为会消耗大量的内存 (进程虚拟内存会占用 4GB [32 位操作系统],而线程也要大约 4MB)。

大量的进程 / 线程出现了新的问题

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

因此我们可以将线程分为 “内核态 “线程和” 用户态 “线程。

一个 “用户态线程” 必须要绑定一个 “内核态线程”,但是 CPU 并不知道有 “用户态线程” 的存在,它只知道它运行的是一个 “内核态线程”。
在这里插入图片描述
这样,我们再去细化去分类一下,内核线程依然叫 “线程 ”,用户线程叫 “协程”。
在这里插入图片描述
既然一个协程可以绑定一个线程,那么能不能多个协程绑定一个或者多个线程上呢。

于是,Go 为了提供更容易使用的并发方法,使用了 goroutine。goroutine 来自协程的概念,让一组可复用的函数运行在一组线程之上,即使有协程阻塞,该线程的其他协程也可以被 runtime 调度,转移到其他可运行的线程上。最关键的是,程序员看不到这些底层的细节,这就降低了编程的难度,提供了更容易的并发。
————————————————
版权声明:本文为CSDN博主「瘦弱的皮卡丘」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ThinPikachu/article/details/121325198

三、协程注意事项

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

所以,在协程中尽量不要调用阻塞IO的方法,比如打印,读取文件,Socket接口等,除非改为异步调用的方式,并且协程只有在IO密集型的任务中才会发挥作用。

要牢记这句话:协程只有和异步IO结合起来才能发挥出最大的威力

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/793836
推荐阅读
相关标签
  

闽ICP备14008679号