• GIL锁、进程池与线程池、同步异步


    GIL锁定义

    GIL锁:Global Interpreter Lock  全局解释器

    本质上是一把互斥锁

    官方解释:

      在CPython中,这个全局解释器锁,也称为GIL,是一个互斥锁,防止多个线程在同一时间执行Python字节码,

      这个锁是非常重要的,因为CPython的内存管理是非线程安全的,也有很多其他的特性依赖于GIL(比如有些

      东西是依赖这个GIL写好的,要替换成本很高),所以即使它影响了程序的效率,也无法直接将其去除。

    需要知道的是,解释器并不只有CPython,还有PyPy,Jpython等等,GIL仅存在于Cpython中,这并不是Python

    这们语言的问题,而是CPython解释器的问题

    GIL解决的问题

    执行py文件的三个步骤

      1.从硬盘加载python解释器到内存

      2.从硬盘加载py文件到内存

      3.解释器解析py文件内容,交给cpu执行

    并且,每当执行一个py文件,就会立即启动一个python解释器

    解释器的作用

      py文件中的内容本质都是字符串,cpu无法执行,只有当字符串被解释器解释成python字节码的时候,

      才能被cpu执行起来,所以,对于一个进程来说,同时只能有一个线程被解释器执行

    内存管理机制

      对于python而言,我们是不需要手动去清除垃圾的,因为python中有一个垃圾回收机制

      垃圾回收机制的原理是依据引用计数

      a = 10  内存中10的地址计数为1

      b = a     10的计数变为2

      b = 1     10的计数变为1

      a = 1     10的计数变为0

      当垃圾回收启动后,会将计数为0的数据清除掉,回收内存(占着内存没有计数的称为内存泄漏)

    产生的问题

      垃圾回收机制本质上也是一串代码,也是需要被解释器解释执行

      所以对于一个开启的进程而言

      不仅有你执行py文件的一个线程,还有着向垃圾回收机制这样的线程

      当多个线程同时并发执行的时候,就会产生一个问题,就是两条线程同时访问统一资源带来的数据错乱问题

      例如:

        主线程定义一个变量,刚使用解释器被cpu执行到开辟空间的时候,引用计数还没有变成1,然后cpu切走了

        然后垃圾回收线程被唤醒了,并且解释器切换到它使用,他一看内存空间有引用计数为0的,就清除了

        当主线程再切回来的时候就懵逼了

    GIL如何解决

      GIL会给解释器上一把锁,当一条线程使用解释器的时候,其他线程无法使用解释器,

      解释器会在cpu切走之前,切换线程,并且会保存上一个线程的一些状态,保证不会被其他线程修改

      这样就让一个系统资源的一个安全性得到保障

    GIL带来的问题

    问题

      由于这个GIL的锁的问题,就导致在同一个进程中,多个线程只能并发执行,并不能做到并行执行

      所以在计算密集型的问题上,CPython的速度要慢于其他高级编程

    为什么要这么设计

      CPython诞生于1991年,多核处理器诞生于2004年,

      所以在CPython诞生的时候,本来就不行并行执行,所以在当时加锁的处理是基本完美的

      为什么拿掉这个锁的设计,在这个期间,很多已经完成的代码都是依赖GIL锁完成的

      如果直接拿掉,代码得重新修改,成本太大

    GIL锁的加锁与解锁时机

    加锁:当一个线程使用解释器时,就立马加锁

    释放:

      1、该线程任务结束

      2、该线程遇到IO操作

      3、该线程使用解释器过程  默认100纳秒(一般比cpu的时间片短)

    关于GIL的性能讨论

    GIL的优点:

      保证了CPython中的内存管理是线程安全的

    GIL的缺点:

      互斥锁的特性使得多线程无法并行

    但我们并不能因此否认Python这门语言,原因如下:

      1、GIL仅仅在CPython解释器中存在,在其他解释器中没有,并不是Python这门语言的缺点

      2、在单核处理器下,多线程之间本来就无法真正的并行执行

      3、在多核处理器下,运算效率的却是比单核处理器高,但要知道现代应用程序多数都是基于网络的

       CPU无法决定网络速度的,当IO操作比较多的时候,多核也需要等待IO操作完成,优势就没那么明显了

    总结:

      1、单核状态下,无论是IO密集还是计算密集型GIL都不会产生任何影响(本来就只能并发)

      2、多核情况下对于IO密集型任务,GIL会有细微的影响,基本可以忽略

      3、CPython中IO密集任务应该采用多线程,计算密集型应该采用多进程

    自定义的线程锁与GIL的区别

     相同点:都是互斥锁

    不同点:GIL锁锁的是解释器内部的资源,例如引用计数,分带回收数据等等,

        但并不能保证我们自定义数据资源的安全性

        所以我们自己开启的共享资源还得自己加锁,保证资源的安全

    进程池与线程池

    池  就是容器,

    进程池:装进程的容器

    线程池:装线程的容器

    好处

      1、自动管理线程的开启和销毁

      2、自动分配任务给空闲的线程

      3、可以限制开启线程的数量,保证系统的稳定

         在这里需要注意的是:这里的限制数量和信号量中的控制并发数量不一样,这里限制的是开启

         线程的最大数量(线程可能还没开启),但是信号量中的线程已经全部开启,控制并发量

    如何使用

      1、创建池子

      2、submit提交任务

      3、pool.shutdown() # 等待所有任务全部完毕,销毁所有线程后关闭线程池(主线程需要等待)

    from concurrent.futures import ThreadPoolExecutor   # 导入类
    
    pool = ThreadPoolExecutor()  # 实例化产生池子对象
    
    def task(name):
        # 代码区
        print(name)
        pass
    
    pool.submit(task,"rock")
    
    pool.shutdown() # 可以让主线程等待线程池里的任务全部完毕,才往下执行

    同步异步

    在并发编程中,经常提及到的名词:阻塞非阻塞,并行并发,同步异步

    阻塞非阻塞

      指的是程序的运行状态,程序运行有三个状态:阻塞态、运行态、就绪态

      阻塞指的是阻塞态  非阻塞可能是运行态或者是就绪态

    并发并行

      指的是多任务状态下处理任务的方式

      并发:多个任务看起来像在同时运行,本质上是切换+保存状态

      并行:真正的同时执行,必须具备多核处理器

    同步异步

      指的是任务提交的方式

      同步:指的是任务发起后,必须在原地等待任务完成,拿到结果,才能接着往下执行,

         这就称之为同步,默认的情况就是同步

         注意:协程也是同步

      异步:指的是任务发起后,不需要再原地等待,可以接着执行其他代码,称之为异步

        异步任务必须依赖并发或者并行  再python中通过多进程、多线程实现

        异步可以一起执行代码,效率明显高于同步

    需要注意的是:

      1、同步不等于阻塞:同步虽然需要在原地等待,但是提交的任务可能一直在运行,那就不是阻塞

      2、卡住不等于阻塞:原因同上

      3、异步不等于非阻塞:提交完任务后,如果两边都遇到了IO操作,那就阻塞了

    异步回调

    异步指的是任务提交的方式是异步的

    异步出现的问题

      如果这个任务需要返回值,我需要拿到返回值处理结果,那么该什么时候去拿返回值

      如果早了,任务还未完成;如果晚了,那么结果就没有得到及时处理

    解决方案

      异步回调

    异步回调指的就是一个函数,该函数会在任务完成后自动被调用,并且会把一个Future对象传进去(任务对象)

    通过Future对象的result()获取执行结果

    import time
    from concurrent.futures import ThreadPoolExecutor   # 导入类
    
    pool = ThreadPoolExecutor()  # 实例化产生池子对象
    
    def task():     # 任务函数
        print("task  run")
        time.sleep(2)
        print("task over")
        return "执行结果"   # 返回结果
    
    def call_back(obj): # 回调函数,调用会自动把Future对象传进去,所以在这需要接收一下
        res = obj.result() # 通过result() 拿到结果
        print(res)
    
    p = pool.submit(task)   # 创建一个Future对象 <Future at 0x200d73230f0 state=running>
    p.add_done_callback(call_back) # Future对象有个方法添加回调函数
    # 这样提交完任务后不仅会被及时处理,而且还不需要等待,可以接着执行其他代码

    通常异步任务都会绑定一个回调函数,用来处理任务结果

    在进程池中,回调函数是在父进程中执行的,进程间数据不共享,需要把数据返回来

    在线程池中,回调函数是由当前执行的线程发起执行的,同一进程中的线程共享资源,可以直接运行

  • 相关阅读:
    oracle用户被锁死
    windows远程桌面智能调整大小
    批量ping测试脚本
    信息的组织和提取方法
    BeautifulSoup
    requests模块学习
    Telerik Fiddler 应用方法
    js 时间格式换成 把字符串(yyyymmdd)转换成日期格式(yyyy-mm-dd)记录
    vuedraggable 拖拽 应用 不同列表之间的拖拽
    vue项目图片上传 vant van-uploader 图片上传
  • 原文地址:https://www.cnblogs.com/hesujian/p/10982573.html
Copyright © 2020-2023  润新知