Python Notes25:进程、线程、协程

Python Notes25:进程、线程、协程

1. 进程

进程是系统资源分配的最小单位。系统由一个个程序,也就是进程组成的,一般情况下,分为文本区域、数据区域和堆栈区域。
文本区域存储处理器执行的代码(机器码),通常来说,这是一个只读区域,防止运行的程序被意外修改。
数据区域存储所有的变量和动态分配的内存,又细分为初始化的数据区(所有初始化的全局、静态、常量,以及外部变量)和为初始化的数据区(初始化为0的全局变量和静态变量),初始化的变量最初保存在文本区,程序启动后被拷贝到初始化的数据区。
堆栈区域存储着活动过程调用的指令和本地变量,在地址空间里,栈区紧连着堆区,他们的增长方向相反,内存是线性的,所以我们代码放在低地址的地方,由低向高增长,栈区大小不可预测,随开随用,因此放在高地址的地方,由高向低增长。当堆和栈指针重合的时候,意味着内存耗尽,造成内存溢出。
进程的创建和销毁都是相对于系统资源,非常消耗资源,是一种比较昂贵的操作。进程为了自身能得到运行,必须要抢占式的争夺CPU。对于单核CPU来说,在同一时间只能执行一个进程的代码,所以在单核CPU上实现多进程,是通过CPU快速的切换不同进程,看上去就像是多个进程在同时进行。
由于进程间是隔离的,各自拥有自己的内存内存资源,相比于线程的共同共享内存来说,相对安全,不同进程之间的数据只能通过 IPC(Inter-Process Communication) 进行通信共享。

2. 线程

线程是CPU调度的最小单位。如果进程是一个容器,线程就是运行在容器里面的程序,线程是属于进程的,同个进程的多个线程共享进程的内存地址空间。
线程间的通信可以直接通过全局变量进行通信,所以相对来说,线程间通信是不太安全的,因此引入了各种锁的场景,不在这里阐述。
当一个线程崩溃了,会导致整个进程也崩溃了,即其他线程也挂了, 但多进程而不会,一个进程挂了,另一个进程依然照样运行。
在多核操作系统中,默认进程内只有一个线程,所以对多进程的处理就像是一个进程一个核心。

3. 同步和异步

同步和异步关注的是消息通信机制,所谓同步,就是在发出一个函数调用时,在没有得到结果之前,该调用不会返回。一旦调用返回,就立即得到执行的返回值,即调用者主动等待调用结果。所谓异步,就是在请求发出去后,这个调用就立即返回,没有返回结果,通过回调等方式告知该调用的实际结果。
同步的请求,需要主动读写数据,并且等待结果;异步的请求,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

4. 阻塞和非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。所以,区分的条件在于,进程/线程要访问的数据是否就绪,进程/线程是否需要等待。
非阻塞一般通过多路复用实现,多路复用有 select、poll、epoll几种实现方式。

5. 协程

协程是属于线程的,又称微线程,纤程,英文名Coroutine。举个例子,在执行函数A时,我希望随时中断去执行函数B,然后中断B的执行,切换回来执行A。这就是协程的作用,由调用者自由切换。这个切换过程并不是等同于函数调用,因为它没有调用语句。执行方式与多线程类似,但是协程只有一个线程执行。
协程的优点是执行效率非常高,因为协程的切换由程序自身控制,不需要切换线程,即没有切换线程的开销。同时,由于只有一个线程,不存在冲突问题,不需要依赖锁(加锁与释放锁存在很多资源消耗)。
协程主要的使用场景在于处理IO密集型程序,解决效率问题,不适用于CPU密集型程序的处理。然而实际场景中这两种场景非常多,如果要充分发挥CPU利用率,可以结合多进程+协程的方式。后续我们会讲到结合点。

5.1 应用场景

外部接收一些文件,每个文件里有一组数据,其中,这组数据需要通过http的方式,发向第三方平台,并获得结果。由于同一个文件的每一组数据没有前后的处理逻辑,在之前通过Requests库发送的网络请求,串行执行,下一组数据的发送需要等待上一组数据的返回,显得整个文件的处理时间长,这种请求方式,完全可以由协程来实现。
Asyncio是一个异步编程的框架,可以解决异步编程,协程调度问题,线程问题,是整个异步IO的解决方案。为了更方便的配合协程发请求,使用aiohttp库来代替requests库
 

程序提速的方法

notion image

Python对并发编程的支持

  1. 多线程:threading,利用CPU和IO可以同时执行(并行执行)的原理,让CPU不会干巴巴等待IO完成
  1. 多进程:multiprocessing ,利用多核CPU的能力,真正的并行执行任务
  1. 异步IO:asyncio,在单线程利用CPU和IO同时执行的原理,实现函数异步执行
  1. 使用Lock对共享资源进行加锁,防止冲突访问e.g. 多线程和多进程同时访问同一个文件,同时写入的话就会出现冲突,我们把文件锁起来就可以实现有序访问
  1. 使用Queue实现不同线程/进程之间的数据通信,实现生产者-消费者模式
  1. 使用线程池Pool/进程池Pool,简化线程/进程的任务提交、等待结束、获取结果
  1. 使用subprocess启动外部程序的进程,并进行输入输出交互e.g. 写好了一个.exe程序,使用这个模块我们就可以调取这个.exe程序并和它进行输入输出交互,实现交互式的进程通信。
 
 
Python-多线程、多进程编程基础
场景1:下载文件,按顺序下载花取1个小时,而采用 并发 下载将时间减少至20分钟 场景2:一个APP应用,优化前每次打开页面需要3s,采用 异步并发 提升到每次200ms 引入并发编程,目的是为了提升程序运行的速度。 在学习后期,应对不同的实际项目,并发编程成了绕不开的话题。 多线程:threading,利用CPU和IO可以同时执行(并行执行)的原理,让CPU不会干巴巴等待IO完成 多进程:multiprocessing ,利用多核CPU的能力,真正的并行执行任务 异步IO:asyncio,在单线程利用CPU和IO同时执行的原理,实现函数异步执行 使用Lock对共享资源进行加锁,防止冲突访问 e.g. 多线程和多进程同时访问同一个文件,同时写入的话就会出现冲突,我们把文件锁起来就可以实现有序访问 使用Queue实现不同线程/进程之间的数据通信,实现生产者-消费者模式 使用线程池Pool/进程池Pool,简化线程/进程的任务提交、等待结束、获取结果 使用subprocess启动外部程序的进程,并进行输入输出交互 e.g. 写好了一个.exe程序,使用这个模块我们就可以调取这个.exe程序并和它进行输入输出交互,实现交互式的进程通信 接下来是一些基础知识的介绍与示例。 概念:多任务是指在同一时间内执行多个任务 优势:充分利用 CPU 资源,提高程序的执行效率 多任务的表现形式: 概念:在一段时间内 交替 执行多个任务 Note: 在单核CPU上,执行多任务一定使用的是并发! 在一段时间内 真正的同时 一起执行多个任务 进程是 资源分配的最小单位 ,他是操作系统进行资源分配和调度运行的基本单位。通俗理解:一个正在运行的一个程序就是一个进程。例如:正在运行的qq、wechat等,它们都是一个进程。 对于一个没有运行的程序并不叫进程一个运行起来的程序至少有一个进程 对于下面的代码: 如果让两个函数同时运行,那么运行效率会大大提高。 Note: 在运行*.py程序时会默认创建一个主进程(main) 这里需要注意: target=process_1和 target=process_1( ) 是完全不同的 前者结果为: 后者结果为: 如果 target=xxx( ):那么( )代表了函数的调用,会直接运行该函数,这样就不在进程中使用了,与我们的想法相悖;而 target=xxx,不带( ) 则代表函数的名字,给进程一个函数的名字,之后让进程根据函数的名字去调用这个函数。 一般我们需要使用多进程执行的任务都是有参数的,上面的例子说了, target=xxx( )使用了( ) 就不能使用多进程执行了,那么我们要怎么解决这个问题呢? 这里的结论可以自行验证,这里不再赘述 这里我们可以使用Python自带的两个语法: args: arguments,参数 -> 元组(tuple)kwargs: keywords arguments,关键字参数 -> 字典(dict) 例子: Note: args=(x, )中的,千万不能省略,不然就不是一个tuple了! 例子: 1.
Python-多线程、多进程编程基础