python进程,线程,协程详解

概念

从计算机硬件角度:
计算机的核心是CPU,承担了所有的计算任务。一个CPU,在一个时间切片里只能运行一个程序。

进程

进程:是CPU对程序的一次执行过程、一次执行任务。各个进程有自己的内存空间、数据栈等。操作系统分配内存的基本单位(打开、执行、保存...)。资源分配的最小单位,线程——程序执行的最小单位。

线程。

线程:是进程中执行运算的最小单位,是进程中的一个实体。(打开、执行、保存...)一个程序至少有一个进程,一个进程至少有一个线程。操作系统分配CPU的基本单位

协程

协程:比线程更小的执行单元,又称微线程,在单线程上执行多个任务,自带CPU上下文用函数切换,开销极小。不通过操作系统调度,没有进程、线程的切换开销。(gevent,monkey.patchall)

举例

我们假设把一个进程比作我们实际生活中的一个拉面馆,负责保持拉面馆运行的服务员就是线程,每个餐桌代表要完成的任务。
当我们用多线程完成任务时,模式是这样的:每来一桌的客人,就在那张桌子上安排一个服务员,即有多少桌客人就得对应多少个服务员;
而当我们用协程来完成任务时,模式却有所不同了: 就安排一个服务员,来吃饭得有一个点餐和等菜的过程,当A在点菜,就去B服务,B叫了菜在等待,我就去C,当C也在等菜并且A点菜点完了,赶紧到A来服务… …依次类推。
从上面的例子可以看出,想要使用协程,那么我们的任务必须有等待。当我们要完成的任务有耗时任务,属于IO密集型任务时,我们使用协程来执行任务会节省很多的资源(一个服务员和多个服务员的区别,并且可以极大的利用到系统的资源。

线程安全

多线程环境中,共享数据同一时间只能有一个线程来操作。

原子操作

原子操作就是不会因为进程并发或者线程并发而导致被中断的操作。

并行和并发

串行:单个CPU核心,按顺序执行
并行:多个CPU核心,不同的程序就分配给不同的CPU来运行。可以让多个程序同时执行。(多进程)
并发:单个CPU核心,在一个时间切片里一次只能运行一个程序,如果需要运行多个程序,则串行执行,遇到IO阻塞就切换,即计算机在逻辑上能处理多任务的能力。(多进程,多线程)

多进程/多线程

表示可以同时执行多个任务,进程和线程的调度是由操作系统自动完成。
进程:每个进程都有自己独立的内存空间,不同进程之间的内存空间不共享。
线程:一个进程可以有多个线程,所有线程共享进程的内存空间,通讯效率高,切换开销小。共享意味着竞争,导致数据不安全,为了保护内存空间的数据安全,引入"互斥锁",“递归锁”,“升序锁”等。

Python的多线程:

GIL:Global Interpreter Lock, 全局解释器锁,线程的执行权限,在Python的进程里只有一个GIL。


一个线程需要执行任务,必须获取GIL。
好处:直接杜绝了多个线程访问内存空间的安全问题。
坏处:Python的多线程不是真正多线程,不能充分利用多核CPU的资源。
但是,在I/O阻塞的时候,解释器会释放GIL。
同步,异步,阻塞,非阻塞



异步

异步本质上是单线程的,因为 IO 操作在很多时候会存在阻塞,异步就是在这种阻塞的时候,通过控制权的交换来实现多任务的。即异步本质上是运行过程中的控制权的交换。最典型的例子就是生产者消费者模型。

同步

即程序协同进行,遇到阻塞就等待,直到任务完成为止。

多进程

:密集CPU任务,需要充分使用多核CPU资源(服务器,大量的并行计算)的时候,用多进程。 multiprocessing

缺陷:

多个进程之间通信成本高,切换开销大。

多线程

:密集I/O任务(网络I/O,磁盘I/O,数据库I/O)使用多线程合适。
threading.Thread、multiprocessing.dummy

缺陷:

同一个时间切片只能运行一个线程,不能做到高并行,但是可以做到高并发。

协程

:又称微线程(一种用户态的轻量级线程),在

单线程

上执行多个任务,用函数切换,由程序自身控制,开销极小。
不通过操作系统调度,没有进程、线程的切换开销。
每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置,不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
当程序中存在大量不需要CPU的操作时(IO),遇到IO操作自动切换到其它协程。
greenlet, gevent,monkey.patchall,yield,async
多线程请求返回是无序的,哪个线程有数据返回就处理哪个线程,而协程返回的数据是有序的。
因为协程是一个线程执行,所以想要利用多核CPU,最简单的方法是多进程+协程,这样既充分利用多核,又充分发挥协程的高效率。
缺陷:单线程执行,处理密集CPU和本地磁盘IO的时候,性能较低。处理网络I/O性能还是比较高.
Python的GIL只能保证原子操作的线程安全,因此在多线程编程时我们需要通过加锁来保证线程安全。
最简单的锁是互斥锁(同步锁),
互斥锁是用来解决IO密集型场景产生的计算错误,即目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据。
递归锁
:就是在一个大锁中再包含子锁
升序锁
:两个线程想获取到的锁,都被对方线程拿到了,那么我们只需要保证在这两个线程中,获取锁的顺序保持一致就可以了。举个例子,我们有线程thread_a, thread_b, 锁lock_1, lock_2。只要我们规定好了锁的使用顺序,比如先用lock_1,再用lock_2,当线程thread_a获得lock_1时,其他线程如thread_b就无法获得lock_1这个锁,也就无法进行下一步操作(获得lock_2这个锁),也就不会导致互相等待导致的死锁。简言之,解决死锁问题的一种方案是为程序中的每一个锁分配一个唯一的id,然后只允许按照升序规则来使用多个锁,这个规则使用上下文管理器 是非常容易实现的。

posted @ 2021-03-16 14:12  愺様  阅读(659)  评论(0编辑  收藏  举报