进程线程协程

01并发和并行

>>>>多任务 

多任务可以通过多进程 多线程 协程来实现

 

CPU和多任务的关系图片

  

>>>>并发和并行

  • 并发指的是任务数多于CPU核数,通过操作系统的各种任务调度算法,实现用多个任务一起执行。

    (实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)

  • 并行指的是任务数小于等于CPU核数,即任务真的是一起执行的。

>>>>同步和异步

  • 同步是指线程在访问某一资源时,获得了资源的返回结果后才会执行其他操作(先做某件事,再做某件事)。

    可以理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B执行,B执行后将结果给A,A再继续操作。

  • 异步与同步相对,是指线程在访问某一资源时,无论是否取得返回结果,都进行下一步操作,当有了资源返回结果时,系统自会通知线程。

 

02线程

>>>>简述python线程的缺陷以及适用场景

因为有GIL锁的存在,python中的多线程在同一时间没办法同时执行(即没办法实现并行)

适用场景:涉及到网络磁盘IO的任务都是IO密集型任务,这类任务的特点是cpu消耗很少

任务的大部分时间都在等到IO操作完成(因为IO的速度要远远低于cpu和内存的速度)

>>>>多线程创建方式一

Thread类可以用来创建线程对象

target:指定线程执行的任务(一般是任务函数)

args kwargs:接受任务函数的参数 args=("aa",) kwargs={"name":zn} name:指定线程的名字

 

>>>>多线程创建方式二 继承类来创建线程

通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,可以通过类来封装,新建一个类,只要继承threading.Thread就可以了,然后重写run方法

说明:threading.Thread类的run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。

而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python解释器进行调度。

当该线程获得执行的机会时,就会调用run方法执行线程

一个线程类只能写一个run方法,如果有多个任务要执行,则要创建多个线程类

 

>>>>多线程共享全局变量

python中的多线程可以共享全局变量
缺点:但是会出现资源竞争,导致全局变量数据不准确。比如在计算时赋值还未完成,线程已经切换。

操作系统如何切换py中的线程?
GIL全局解释器锁 同一时间只会执行一个线程,如果线程要执行必须要先获取全局解释器锁
1.遇到耗时等待 例如time.sleep(1)会自动释放GIL锁
2.当线程执行时间达到一定的阈值,会自动释放GIL锁
所以线程没有办法并行只能并发

 

>>>>互斥锁解决资源竞争问题

互斥锁为资源引入一个状态:锁定/非锁定

某个线程要更改共享数据时,先将其锁定,此时资源的状态为锁定,其他线程不能更改直到该线程释放资源,将资源的状态变成非锁定,其他的线程才能再次锁定该资源。

互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的准确性
threading模块定义了Lock类,可以方便的处理锁定:

 

 

注意:如果这个锁之前是没有上锁的。那么acquire不会堵塞

如果在调用acquire这个锁上锁之前 他已经被其他线程上了锁那么此时acquire堵塞,直到这个锁被解锁为止

 

>>>>死锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁

尽管死锁很少发生,但一旦发生就会造成应用的停止响应

使用线程多的时候一定要避免出现死锁

 

03队列

使用队列来实现线程间数据的同步,可以确保数据的安全

queue模块只能在一个进程的多个线程中使用三种队列

>>>>先入先出

 

 

>>>>后入先出

>>>>优先级队列

队列中的数据为元祖类型,元祖的第一个元素表示数据的优先级,优先级越小的越先出来

关于优先级,尽量使用数值,如果全是字符串,会按ASCII码进行排序

quene模块一个队列只能在一个进程中使用 一个进程中多个线程使用

>>>>队列在多线程中的应用

 

 

04 进程

 

进程是操作系统资源分配的基本单位,一个进程中可以有多个线程
线程:线程是操作系统任务调度的基本单位

 

多个进程可以同时进行
每个进程之间资源是独立的

>>>>进程和线程对比

 

功能
进程 能够完成多任务,比如在一台电脑上能够同时运行多个软件
线程 能够完成多任务,比如一个qq中的多个聊天窗口

定义的不同

 

进程是系统进行资源分配和调度的一个独立单位

 

线程是进程的一个实体,是CPU调度和任务分派的基本单位,它比进程更小的能独立运行的基本单位。

 

线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源,但是它可与同属一个进程的其他线程共享进程所拥有的全部资源

 

python中同一个进程中的线程 是没办法并行的(GIL),进程是可以并行的,不同进程中的线程也是可以并行的

 

 

 

 

 

 

 

 

 >>>>多进程不可共享全局变量

 

Python 多进程默认不能共享全局变量,因为进程的资源是独立的

主进程与子进程是并发执行的,进程之间默认是不能共享全局变量的(子进程不能改变主进程中全局变量的值)

 

 

 

>>>>多进程之间通信

进程之间通信:使用队列

multiprocessing.Queue:可以多个进程之间共用(通用)跨进程通讯 队列要在主进程中创建 当成参数传给子进程

queue.Queue模块只能在一个进程中使用 一个进程中多个线程使用

 

 

 

>>>>通过继承自定义进程类

 

 

 

>>>>获取进程id

 

 

 

>>>>进程和队列使用实例

 

 

 

05 协程

协程又称微线程,是python中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)。

为啥说他是一个执行单元,因为它自带CPU上下文,这样只要在合适的gr时机,我们可以把一个协程切换到另一个协程。只要这个过程中保存或恢复CPU上下文那么程序还是可以运行的。

通俗的描述协程是线程中的一个特殊的函数,这个函数执行的时候,可以在某个地方暂停,并且可以重新在暂停处继续运行。

协程在进行切换的时候,只需要保存当前协程函数中的一些临时变量信息,然后切换到另外一个函数中执行,并且切换的次数以及什么时候再切换回原来的函数,都由开发者自己决定。

协程切换的时候既不涉及到资源切换们也不涉及到操作系统的调度,而是在同一个程序中切换不同的函数执行。

所以协程占用的资源非常少,切换的时候几乎不耗费什么资源,一秒钟切换个上万次系统都扛得住。

所以说协程与进、 线程相比不是一个维度的概念。

 

>>>>原生的协程实现多任务 一般不用(了解)

协程函数的定义和调用

async 加在def前面定义协程函数

await:只能写在协程函数中 await后面必须是一个可等待对象,协程任务 asyncio.sleep()

 

 

 

 

 

 

>>>>greenlet模块实现多任务 一般不用(了解)

为了更好的使用协程来完成多任务,python中的greenlet模块对其封装,从而使得切换任务变得简单,只能手动去切换

 

 

 

>>>>gevent模块 使用协程要使用的模块

gevent模块又对greenlet进行了一层封装

当程序遇到io耗时等待的时候 会自动进行切换

gevent中默认是遇到gevent.sleep()会进行切换

如果让gevent遇到io自动切换,节省运行时间 需要在程序的导包处加一个monkey补丁,注意:只能在单线程中用,不支持多线程,加了monkey补丁,遇到time.sleep也会自动切换

from gevent import monkey monkey.patch_all() 两行代码要在文件的最上层引入

线程的切换:耗时io操作 网络磁盘 input output 协程切换:遇到io操作

 

 


06进程线程协程对比

进程、线程、协程对比:

    • 进程是资源分配的单位;
      线程是操作系统调度的单位;
      协程又名微线程,存在于线程之中

    • 进程切换需要的资源最大效率很低;
      线程切换需要的资源一般效率一般当然在不考虑G I L锁的情况下;
      协程切换任务资源很小,效率高

    • 多进程多线程。根据CPU核数不一样,只有多进程能实现并行,但是协程是在一个线程中,所以是并发

    • 注意点Python中的线程由于GIL锁的存在,并不能够实现并行,要充分利用多核C P U还是需要使用进程来做。


07进程池

当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态生成多个进程。

但如果是成百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的pool方法。

初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行请求。

但是如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务。

 

 

>>>>进程池之间的队列 

进程池和进程池之间进行通讯使用进程池里的队列
进程池的Queue 如果要使用Pool创建进程,就需要使用multiprocessing.Manager().Queue(),而不是multiprocessing.Queue(),否则会报错。

 

 

>>>>concurrent.futures实现进程池 线程池

 

https://zhuanlan.zhihu.com/p/111587914

posted @ 2021-06-23 18:40  北京测试菜鸟  阅读(56)  评论(0编辑  收藏  举报