python(48): 进程,线程 ,协程

区别

进程拥有代码和打开的文件资源、数据资源、独立的内存空间。

线程:线程从属于进程,是程序的实际执行者。一个进程至少包含一个主线程,也可以有更多的子线程。线程拥有自己的栈空间。

对操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元。

进程--资源分配最小单位,线程,资源调度最小单位

协程:英文Coroutines,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。

最重要的是,协程不是被操作系统内核所管理,而完全是由程序(用户/库)所控制(也就是在用户态执行)。

这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。

资源对比

进程:拥有自己独立的堆和栈,既不共享堆,也不共享栈,进程由操作系统调度;

线程:拥有自己独立的栈和共享的堆,共享堆,不共享栈,标准线程由操作系统调度;

协程:拥有自己独立的栈和共享的堆,共享堆,不共享栈,协程由程序员在协程的代码里显示调度

 

进程

进程与资源

学习 Linux 时,经常可以看到两个词:User space(用户空间)和 Kernel space(内核空间)。

简单说,Kernel space 是 Linux 内核的运行空间,User space 是用户程序的运行空间。为了安全,它们是隔离的,即使用户的程序崩溃了,内核也不受影响。

地址空间

4GB 的进程虚拟地址空间被分成两部分:用户空间和内核空间

用户空间

用户空间按照访问属性一致的地址空间存放在一起的原则,划分成 5个不同的内存区域。 访问属性指的是“可读、可写、可执行等 。

  • 代码段

代码段是用来存放可执行文件的操作指令,可执行程序在内存中的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,它是不可写的。

  • 数据段

数据段用来存放可执行文件中已初始化全局变量,换句话说就是存放程序静态分配的变量和全局变量。

  • BSS段

BSS段包含了程序中未初始化的全局变量,在内存中 bss 段全部置零。

  • 堆 heap

堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

  • 栈 stack

栈是用户存放程序临时创建的局部变量,也就是函数中定义的变量(但不包括 static 声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

上述几种内存区域中数据段、BSS 段、堆通常是被连续存储在内存中,在位置上是连续的,而代码段和栈往往会被独立存放。堆和栈两个区域在 i386 体系结构中栈向下扩展、堆向上扩展,相对而生。

你也可以再 linux 下用size 命令查看编译后程序的各个内存区域大小:

[lemon ~]# size /usr/local/sbin/sshd
   text    data     bss     dec     hex filename
1924532   12412  426896 2363840  2411c0 /usr/local/sbin/sshd

内核空间

在 x86 32 位系统里,Linux 内核地址空间是指虚拟地址从 0xC0000000 开始到 0xFFFFFFFF 为止的高端内存地址空间,总计 1G 的容量, 包括了内核镜像、物理页面表、驱动程序等运行在内核空间 。

线程

线程是操作操作系统能够进行运算调度的最小单位。线程被包含在进程之中,是进程中的实际运作单位,一个进程内可以包含多个线程,线程是资源调度的最小单位。

线程资源和开销

同一进程中的多条线程共享该进程中的全部系统资源,如虚拟地址空间,文件描述符文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈、寄存器环境、线程本地存储等信息。

线程创建的开销主要是线程堆栈的建立,分配内存的开销。这些开销并不大,最大的开销发生在线程上下文切换的时候。

特点:

多线程并发时占用cpu时间由系统调度,所以多线程编程时需要考虑线程安全。

协程 

类比一个进程可以拥有多个线程,一个线程也可以拥有多个协程,因此协程又称微线程和纤程。

Python 和 Go 从语言层面提供了对协程很好的支持 

主动让出CPU与系统分配

人们发现经常需要异步操作共享资源的情况下,主动让出时间片的协程模式比线程抢占式分配的效率要好,也更简单。

从实际开发角度看,与线程相比,这种主动让出型的调度方式更为高效。一方面,它让调用者自己来决定什么时候让出,比操作系统的抢占式调度所需要的时间代价要小很多。后者为了能恢复现场会在切换线程时保存相当多的状态,并且会非常频繁地进行切换。另一方面,协程本身可以做成用户态,每个协程的体积比线程要小得多,因此一个进程可以容纳数量相当可观的协程任务。

 

从代码结构上看,协程保证了编写过程中的思维连贯性,使得函数(闭包)体本身就无缝保持了程序状态。逻辑紧凑,可读性高,不易写出错的代码,可调试性强。

但归根结底,单核处理器还是同时间只能做一件事,所以同一时间点还是只能有一个协程任务运行,它和线程的最主要差别就是,协程是主动让出使用权,而线程是抢占使用权,即所谓的,协程是用户态,线程是系统态。

调度开销

线程是被内核所调度,线程被调度切换到另一个线程上下文的时候,需要保存一个用户线程的状态到内存,恢复另一个线程状态到寄存器,然后更新调度器的数据结构,这几步操作设计用户态到内核态转换,开销比较多。

协程的调度完全由用户控制,协程拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作用户空间栈,完全没有内核切换的开销。

python协程的几个概念:

event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足条件发生的时候,就会调用对应的处理方法。

coroutine:中文翻译叫协程,在 Python 中常指代为协程对象类型,我们可以将协程对象注册到时间循环中,它会被事件循环调用。我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。

task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。

future代表将来执行或没有执行的任务的结果,实际上和 task 没有本质区别。

async关键字async 定义一个协程;

await 关键字用来挂起阻塞方法的执行。

注意事项:在特殊函数内部不可以出现不支持异步模块相关的代码。(例:time,request)

协程优点缺点

优点

1.无需线程上下文切换的开销,协程避免了无意义的调度,由此可以提高性能(但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力)
2.无需原子操作锁定及同步的开销

缺点

1.无法利用多核

2.进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

适用场景:爬虫程序,IO密集

爬虫协程:Python爬虫进阶 | 异步协程 - PythonGirl - 博客园

爬虫场景多协程与多线程比较:

多线程情况时,发起请求,此线程会等待响应,系统切换其他线程执行

多协程时,一个协程序发起请求后,代码级别切换执行另一个协程,能充分利用线程工作,并且没有系统上下文切换开销。

问题:

为什么线程是操作系统调度的最小单元?
一条线程是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

参考

并发异步编程之争:协程(asyncio)到底需不需要加锁?

干货 | 进程、线程、协程 10 张图讲明白了! - 知乎

线程,进程,协程详细解释_幽雨雨幽的博客-CSDN博客

python 协程与异步IO - 刘江的python教程

https://www.jianshu.com/p/7c851145ee4c

理解Python的协程(Coroutine) - 掘金

  • 2
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值