zoukankan      html  css  js  c++  java
  • python37 1.GIL--全局解释器锁 2.GIL带来的问题 3.为什么需要GIL 4.GIL的加锁解锁时机 5.关于GIL的性能的讨论 6.线程常用方法 7.GIL锁与自定义锁的区别 8.进程池与线程池 9.同步异步 10.异步调用

    复习
    1.JoinableQueue--可以被join的队列
    2.多线程
    3线程的使用方法与进程一模一样
    3.1守护线程
    3.2线程安全问题
    3.3解决方案
    3.3.1互斥锁mutex
    3.3.2递归锁Rlock
    3.3.3信号量semaphore
    3.3.4死锁问题

    详解:
    1.JoinableQueue--可以被join的队列
    1.1join是等待任务结束
    队列怎么叫结束
    调用task_done一次则表示有一个数据被处理完成了,当task_done次数等于put的次数就意味着任务处理完成了

    1.2这就是join的执行时机
    该队列已经明确告知数剧的使用方,所有数据都已经处理完成

    1.3在生产者消费者模型中解决了消费者,不知道何时算是任务结束的问题
    具体过程:主进程先等待所有的生产者进程生成完毕,再等队列中的数据被全部处理,这就意味着,任务全部结束

    2.多线程
    2.1使用多线程|多进程目的:
    为了 并发执行任务,从而提高效率
    2.2什么是线程?
    线程是操作系统运算调度的最小单位(CPU最小执行单位),线程被包含在进程中,一个线程就是一个固定的执行流程(控制流)
    2.3线程的特点:
    进程是不能被执行的,进程是一个资源单位,其中 包含了程序运行所需的所有资源
    线程:才是真正的执行单位,光有进程和程序是无法运行的,必须先创建进程将资源加载到进程中,再启动线程来执行任务
    一个进程至少包含一个线程,称之为主线程,主线程是由操作系统来开启的
    一个进程可以包含多个线程来提高程序的效率
    2.4线程与进程的区别
    1.线程创建的开销远小于进程
    2.统一进程中的所有线程共享进程内的资源
    3.线程之间没有父子关系,是平等的,PID相同
    2.5如何选择进程与线程?
    要根据具体的任务类型,IO密集(线程) 计算密集(进程)
    3.线程的使用方法与进程一模一样
    区别:开启线程的位置可以是任意位置
    3.1守护线程
    守护线程会在所有非守护线程结束时一起结束,当然守护可以提前结束(任务完成的情况下)
    3.2线程安全问题
    问题:并发操作同一个资源,可能导致数据错乱
    解决方案:加互斥锁
    3.3解决方案
    1.互斥锁mutex--lock

    2.递归锁Rlock
    同一线程可以多次执行acquire()
    3.信号量semaphore--仅用于控制并发访问
    4.死锁问题:
    不止一个锁,分别被不同的线程持有,相互等待对方释放,就会导致锁死问题
    如何避免死锁问题?
    锁不要有多个,一个足够
    如果真的发生了死锁问题,必须迫使一方先交出锁




    今日内容
    1.GIL--全局解释器锁
    2.GIL带来的问题
    3.为什么需要GIL
    4.GIL的加锁解锁时机
    5.关于GIL的性能的讨论
    6.线程常用方法
    7.GIL锁与自定义锁的区别
    8.进程池与线程池
    9.同步异步
    10.异步调用
    详解:
    1.GIL--全局解释器锁
    官方解释:
    '''
    In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
    native threads from executing Python bytecodes at once. This lock is necessary mainly
    because CPython’s memory management is not thread-safe. (However, since the GIL
    exists, other features have grown to depend on the guarantees that it enforces.)
    '''
    释义:
    在CPython 中,这个全局解释器锁,也称之为GIL,是一个互斥锁,防止多个线程在同一时间执行Python字节码,这个锁是非常重要的,因为CPython的内存管理非线程安全的,很多其他的特性依赖于GIL,所以即使他影响了程序效率也无法将其直接去除
    非线程安全:即多个线程访问同一个资源,会有问题
    线程安全:即多个线程访问同一个资源,不会有问题
    作用:CPython中有一个互斥锁,防止线程同一时间执行python代码
    注意:
    该锁只存在CPython中,这并不是Python这门语言的问题,而是解释器的问题,除了CPython,还有JPython,pypy解释器
    总结:在CPython中,GIL会把线程的并行变成串行,导致效率降低

    2.GIL带来的问题
    GIL全局解释器锁 本质:是一把互斥锁
    前提条件:
    1.解释器的作用(py文件中的内容本质都是字符串,只有在被解释器解释时,才具备语法意义)
    2.解释器与应用程序代码之间的关系(解释器会将py代码翻译为当前系统支持的指令交给系统执行。)
    影响:
    1.当进程中仅有一条线程时,GIL锁存在不会有任何影响
    2.但是如果进程中有多个线程时,GIL锁就开始发挥作用:
    1.开启子线程时,子线程指定一个target表示该子线程要处理的任务即将要执行的代码,
    2.代码要执行必须交给解释器,即多个线程之间就要共享解释器,
    3.为了避免共享带了的数据竞争问题,于是就给解释器加上了互斥锁。
    总结:对应用程序造成什么影响?
    由于互斥的特性,程序串行,保证了数据安全,降低了执行效率,GIL将使得程序整体效率降低
    为什么Cpython要这么设计?
    Cpython诞生于1991年 而多核处理器诞生2004年
    当时不需要考虑多核效率问题
    现在为什么不拿掉这个锁,因为这期间,很多已经完成的代码都依赖于这个锁,如果直接拿到,这些代码全得该,成本太大了

    3.为什么需要GIL?
    1.GC:内存管理机制
    在使用python进行编程时,程序员无需参与内存管理,因为有Python自带的内存管理机制,简称GC
    2.Python内存管理--使用引用计数 了解
    垃圾回收机制:
    Python中不需要手动管理内存,C,OC
    引用计数:
    每个数会被加上一个整型的计数器,表示这个数据被引用的次数
    a=10 10地址次数计数为1
    b=a 计数2
    b=1 计数1
    a=0 计数0 表示该数据已无人使用,变为垃圾数据
    内存管理:
    当内存占用达到阈值,GC停止其他线程,启动垃圾清理操作(一串代码。一个线程)
    当垃圾回收启动后会将计数为0的数据清除掉,回收内存
    分代回收:
    自动垃圾回收其实就是说,内部会有一个垃圾回收线程,会在某一时间运行起来,开始清理垃圾
    这可能会产生问题:
    例如线程1申请了内存,但是还没有使用CPU切换到了GC,GC将数据当成垃圾清理掉了
    解决问题:
    为了解决问题,CPython就给解释器加了互斥锁,多个线程将不可能同一时间使用解释器,保证了数据的安全性
    4.GIL的加锁解锁时机
    加锁:
    只有一个线程要使用解释器就立马枷锁(即:在调用解释器时立即加锁)
    释放:
    ​ 该线程任务结束
    ​ 该线程遇到IO
    ​ 该线程使用解释器过长(超过设定值) 默认100纳秒
    5.关于GIL的性能的讨论
    5.1GIL:
    优点:保证了CPython中的内存管理是线程安全的
    缺点:互斥锁的特性使得多线程无法并行,严重降低了运行效率
    5.2但我们也不能就此给Python语言全部判负,其原因如下:
    1. GIL仅仅在CPython解释器中存在,在其他的解释器中没有,并不是Python这门语言的缺点
    2. 在单核处理器下,多线程之间本来就无法真正的并行执行
    3. 在多核处理下,运算效率的确是比单核处理器高,但是要知道现代应用程序多数都是基于网络的(qq,微信,爬虫,浏览器等等),CPU的运行效率是无法决定网络速度的,而网络的速度是远远比不上处理器的运算速度,则意味着每次处理器在执行运算前都需要等待网络IO,这样一来多核优势也就没有那么明显了
    5.3GIL给我们造成的影响
    多线程不能并行
    案例:
    1.IO密集型---使用多线程
    有一个下载任务 要从网络中下载一个文件 大小1G
    和转换任务 使用input 转为大写输出
    上述任务并行执行,耗时也不会有太大提升,反而开启多进程会浪费更多资源
    这种任务称之为IO密集型,大量的时间都花在IO等待
    2.计算机密集型任务(即IO操作较少大部分都是计算任务。)
    图像处理,语音处理,大数据分析
    总结:**
    1.单核下无论是IO密集还是计算密集GIL都不会产生任何影响
    2.多核下对于IO密集任务,GIL会有细微的影响,基本可以忽略
    3.Cpython中IO密集任务应该采用多线程,计算密集型应该采用多进程
    注意:
    1.之所以广泛采用CPython解释器,就是因为大量的应用程序都是IO密集型的,
    2.还有另一个很重要的原因是CPython可以无缝对接各种C语言实现的库
    # 解决方案: `*****`
    ​ 区分任务类型
    ​ 1.如果是IO密集使用多线程

    ​ 2.如果是计算密集使用多进程
    6.线程常用方法
    # 1.print(active_count())获取存活的线程数量
    # 2.print(current_thread().getName())获取线程的名称
    # 3.print(enumerate())获取正常运行的所有线程对象
    # 4.print(current_thread())获取当前线程对象

    7.GIL锁与自定义锁的区别
    都是互斥锁
    为什么有了GIL还需要自己加锁
    ​ GIL是加在解释器上的,只能锁住,解释器内部的资源,但是无法锁住我们自己开启资源,所以当程序中出现了共享自定义的数据时就要自己加锁

    8.进程池与线程池******
    池就是容器
    本质上就是装线程|装进程的容器
    池子中存储线程还是进程?
    1.如果是IO密集型任务使用线程池, 2.如果是计算密集任务则使用进程池
    优点:
    1.自动管理线程的开启和销毁
    2.自动分配任务给空闲的线程
    3.可以限制开启线程的数量,保证系统的稳定
    注:信号量中是限制同时并发多少,但是线程已经全都建完了
    如何使用?
    1.创建池子
    pool = ProcessPoolExecutor()
    pool=ThreadPoolExecutor
    2.submit提交任务
    pool.submit(task,"jerry")
    3.pool.shutdown()#等待所有任务完毕,销毁所有线程,后关闭线程池
    pool.shutdown()
    注:关闭后就不能提交新任务了
    9.同步 异步
    ​ 9.1在并发中 经常提及的几个概念
    阻塞--非阻塞 程序的状态
    ​ 程序的运行状态 非阻塞 可能就绪 或者 运行
    并发--并行 多任务处理方式
    ​ 多个任务看起来像是同时运行 ,本质是切换+保存状态
    ​ 并行真正的同时进行中, 必须具备多核处理器
    同步--异步 任务提交执行的方式
    同步==阻塞 × 卡住 == 阻塞 ×
    异步 == 非阻塞 ×
    同步:指的是发起任务后,必须在原地等待,直到任务完成拿到结果
    默认情况就是同步
    异步:发起任务,不需等待结果,可以继续执行其他代码,异步
    异步必须依赖并发或者并行,在python中,通过多线程或多进程
    异步的效率明显高于同步
    如何使用?
    #1. res = pool.submit(task).发起一个异步任务 # 异步调用 res就是一个表示异步任务的对象
    # 2.定义一个回调函数 传的参数就是一个完成后任务对象
    def finished(arg):
    print(arg.result())
    print("黑牛买回来了! ")
    pass
    # 3.res.add_done_callback(finished)给这个异步任务添加了一个回调函数


    案例说明:
    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    import time
    from concurrent.futures._base import Future
    pool = ThreadPoolExecutor() # my cpu_count = 6 so 6 * 5 = 30
    def task():
    time.sleep(2)
    print("执行完成!")
    return "一瓶黑牛!"

    print("start")
    # task()# 同步调用

    # 1.发起一个异步任务
    res = pool.submit(task) # 异步调用 res就是一个表示异步任务的对象
    # 2.定义一个回调函数 传的参数就是一个完成后任务对象
    def finished(arg):
    print(arg.result())
    print("黑牛买回来了! ")
    pass
    # 3.给这个异步任务添加了一个回调函数
    res.add_done_callback(finished)
    # pool.shutdown() # 阻塞直到线程池所有任务全部完成 会导致主线卡在原地
    # print(res)

    # print("执行的结果:",res.result()) # 会导致主线卡在原地

    print("over")
    10.异步回调
    ​ 异步指的是任务的提交方式是异步的
    10.1 异步任务的问题:
    ​ 如果这个任务执行完成后会产生返回值,任务发起方该何时去获取结果
    解决方案: 异步回调

    10.2异步回调
    指的就是一个函数,该函数会在任务后自动被调用,并且会传入Future对象 ,
    通过Future对象的result()获取执行结果 ,
    有了回调函数 就可以在任务完成时 及时处理它
    10.3通常异步任务都会绑定一个回调函数,用来处理任务结果
    1.在进程池中回调函数是在父进程中执行,原因是 任务是由父进程发起的,所以结果也应该交给父进程
    2.在线程池中回调函数就在子线程中执行,原因是 线程之间数据本来是共享的
    3.如果你的任务结果需要交给父进程来处理,那建议回调函数,回调函数会自动将数据返回给父进程,不需要自己处理IPC
  • 相关阅读:
    小记---------sparkRDD的Transformation 和 Action 及案例 原理解释
    小记---------maxwell启动闪退问题
    小记---------spark组件与其他组件的比较 spark/mapreduce ;spark sql/hive ; spark streaming/storm
    kettle 创建作业发送邮件
    oracle查询表的结构
    ETL简介
    Oracle中分析函数
    谷歌浏览器快捷键
    Kettle入门
    Oracle基本知识
  • 原文地址:https://www.cnblogs.com/llx--20190411/p/10982432.html
Copyright © 2011-2022 走看看