zoukankan      html  css  js  c++  java
  • Python-GIL 进程池 线程池





    5、GIL vs 互斥锁(*****)
    1、什么是GIL(Global Interpreter Lock)
    GIL是全局解释器锁,是加到解释器身上的,保护的就是解释器级别的数据 (比如垃圾回收的数据)
    同一个进程内的所有线程都需要先抢到GIL锁,才能执行解释器代码
    2 为什么需要GIL
    python 中内存管理依赖于 GC(一段用于回收内存的代码) 也需要一个线程
    除了你自己开的线程 系统还有一些内置线程 就算你的代码不会去竞争解释器 内置线程也可能会竞争
    所以必须加上锁
    3、GIL的影响
    GIl会限制同一进程的内的多个线程同一时间只能有一个运行,也就是说python一个进程内的多线线程

    无法实现并行的效果,即无法利用多核优势

    然后多核提供的优势是同一时刻有多个cpu参与计算,意味着计算性能地提升,也就是说我们的任务是
    计算密集型的情况下才需要考虑利用多核优势,此时应该开启python的多进程

    在我们的任务是IO密集型的情况下,再多的cpu对性能的提升也用处不大,也就说多核优势在IO密集型程序面前
    发挥的作用微乎其微,此时用python的多线程也是可以的

    GIL的优缺点:
    优点:
    保证Cpython解释器内存管理的线程安全
    缺点:
    同一进程内所有的线程同一时刻只能有一个执行,无法利用多核CPU
    也就说Cpython解释器的多线程无法实现并行

    (问题: 一个py程序 要想运行 必须运行解释器 解释器的工作时翻译代码 并执行
    当一个py进程中 有多个线程 线程的任务就是执行代码 意味者 多个线程都要使用解释器
    简单的说 多线程会争抢解释器的执行权
    如果是自己开的线程 多线程要访问相同数据 加锁就能解决
    但是有一写代码不是程序员写的 也确实需要共享使用 就是解释器
    GC:垃圾回收器 负责清理内存中的无用数据 清理垃圾也需要执行代码 但是GC不应该卡住用户的代码执行
    只能开线程
    GC 看到 x = 10 x = 1 准备删除10 这时候突然CPU切到用户线程 a = 10 此此时还没有问题
    紧接着 CPU 又切到GC GC上来就删除10 在切到用户线程 a 所指向的地址被清理了 产生错误
    解决方案: 给解释器加上锁 保证GC执行期间 用户线程不能执行)


    4、GIL vs 自定义锁
    保护不同的数据就应该加不同的锁。
    相同点:都是互斥锁
    不同点:
    GIL解释器级别锁 锁的是解释器代码
    自定义锁 锁的是自己写的代码
    GIL 在当一个线程调用解释器时 自动加锁 在IO阻塞时或线程代码执行完毕/执行时间过长3ms时 自动解锁

    本质就是一个互斥锁,然后保护不同的数据就应该用不同的互斥锁,保护我们应用程序级别的数据必须自定义互斥锁
    有了GIL 为什么还需要自定义锁?
    GIL 不清楚什么代码会造成数据竞争问题 不知道什么地方该加

    6 Cpython的解释器下,多线程是鸡肋?*****
    多个任务是IO密集型:多线程 (IO的速度 明显要比CPU执行速度慢)
    多个任务是计算密集型:多进程
    7、死锁现象与递归锁(可重入锁),信号量(**)
    死锁?
    进程也有死锁与递归锁,在进程那里忘记说了,放到这里一切说了额
    所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,
    若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,
    这些永远在互相等待的进程称为死锁进程,如下就是死锁

    解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
    这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,
    从而使得资源可以被多次require。直到一个线程所有的acquire都被release,
    其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

    mutexA=mutexB=threading.RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,
    则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止

    死锁造成的问题.程序卡死
    一个锁不会产生死锁
    当有多个锁多个线程时会产生死锁
    a b 锁
    p k 线程
    当p 和 k 都需要a和b锁时才可能产生死锁

    递归锁(可重入锁)RLock
    同一个线程可以多次执行acquire 执行一次acquire 计数加1
    执行一次release 次数减一 执行acquire的次数需要与release的次数对应
    在执行被锁的代码时 同一个线程 不会判断次数 其他线程需要判断 计数为0才可以执行

    不是用来解决死锁的

    Semaphore信号量(了解) 常用在线程中
    信号量作用:限制同时执行被锁代码的线程数量
    案列:
    sem = semaphore(2)
    acquire
    code.....
    release
    开了十个线程 只能有两个同时执行

    8、队列queue(***)
    queue 这个queue和进程里的Queue不同 就是一个简单的容器
    队列是一种数据的容器
    特点:先进先出
    queue先进先出
    lifoqueue先进后出
    priorityqueue 优先级队列 整型表示优先级 数字越大优先级越低

    import queue
    q = queue.Queue()# 普通队列 先进先出
    q.put("a")
    q2 = queue.LifoQueue()# 堆栈队列 先进后出 后进先出 函数调用就是进栈 函数结束就出栈 递归造成栈溢出
    q3 = queue.PriorityQueue() # 优先级队列

    9、Event事件(**)了解
    python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。

    事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,
    那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。

    是什么?
    线程间通讯的方式
    为什么用?
    简化代码
    set()设置为True
    wati()阻塞 直到为True
    clear:将“Flag”设置为False

    4、池(*****)
    就是一个装进程/线程的容器
    为何要用池:
    操作系统无法无限开启进程或线程
    池作用是将进程或线程控制操作系统可承受的范围内
    什么时候用进程池: (比如 双十一)
    当程序中有多个进程时 管理变得非常麻烦
    进程池可以帮我们管理进程
    1.进程的创建
    2.进程的销毁
    3.任务的分配
    4.限制最大的进程数 保证系统正常运行

    池内装的东西有两种:
    装进程:进程池
    装线程:线程池

    进程池
    在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,
    并行操作可以节约大量的时间。多进程是实现并发的手段之一,需要注意的问题是:
    1 很明显需要并发执行的任务通常要远大于核数
    2 一个操作系统不可能无限开启进程,通常有几个核就开几个进程
    3 进程开启过多,效率反而会下降(开启进程是需要占用系统资源的,而且开启多余核数目的进程也无法做到并行)
    例如当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,
    十几个还好,但如果是上百个,上千个。。。手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。

    我们就可以通过维护一个进程池来控制进程数目,比如httpd的进程模式,规定最小进程数和最大进程数...
    创建进程池的类:如果指定numprocess为3,则进程池会从无到有创建三个进程,
    然后自始至终使用这三个进程去执行所有任务,不会开启其他进程


    使用方式?
    ThreadPoolExecutor 线程池
    实例化 时指定最大线程数
    ProcessPoolExecutor 进程池
    实例化 时指定最大进程数
    执行submit来提交任务
    from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
    p=ThreadPoolExecutor(4) # 默认开启的线程数是cpu的核数*5
    p.submit(task,i)

    总结一下:
    进程池可以自动创建进程
    进程限制最大进程数
    自动选择一个空闲的进程帮你处理任务

    进程什么时候算是空闲?
    代码执行完算是空闲

    进程池,池子内什么时候装进程:并发的任务属于计算密集型
    线程池,池子内什么时候装线程:并发的任务属于IO密集型


    回调函数:
    需要回调函数的场景:进程池中任何一个任务一旦处理完了,就立即告知主进程:
    我好了额,你可以处理我的结果了。主进程则调用一个函数去处理该结果,该函数即回调函数

    我们可以把耗时间(阻塞)的任务放到进程池中,然后指定回调函数(主进程负责执行),
    这样主进程在执行回调函数时就省去了I/O的过程,直接拿到的是任务的结果。

    如果在主进程中等待进程池中所有任务都执行完毕后,再统一处理结果,则无需回调函数
  • 相关阅读:
    VysorPro助手
    Play 2D games on Pixel running Android Nougat (N7.1.2) with Daydream View VR headset
    Play 2D games on Nexus 6P running Android N7.1.1 with Daydream View VR headset
    Native SBS for Android
    ADB和Fastboot最新版的谷歌官方下载链接
    How do I install Daydream on my phone?
    Daydream Controller手柄数据的解析
    蓝牙BLE传输性能及延迟分析
    VR(虚拟现实)开发资源汇总
    Android(Java)控制GPIO的方法及耗时分析
  • 原文地址:https://www.cnblogs.com/du-jun/p/9947261.html
Copyright © 2011-2022 走看看