• 学习总结(三十五)


    1.什么是GIL

                 GIL称为全局解释器锁,是一个互斥锁,防止多个线程在同一时间执行python字节码,这个锁非常重要的,因为Cpython的内存管理

           非线程安全的,很多其他的特性依赖于GIL所以即使它影响了程序效率也无法将其直接取出

    2.GIL带来的问题

                由于互斥锁的特性,程序串行,保证数据安全,降低执行效率,GIL将使得程序整体效率降低

    3.为什么需要GIL

               多个线程将不可能在同一时间使用解释器,从而保证了解释器的数据安全。

    4.关于GIL的性能讨论

                  优点:保证了Cpython中的内存管理是线程安全

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

                 单核下无论是IO密集型还是计算机密集型的GIL都不会产生任何影响

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

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

            另外:之所以广泛采用CPython解释器,就是因为大量的应用程序都是IO密集型的,还有另一个很重要的原因是CPython可以无缝对接各种C语言实现的库,这对于一些数学计算相关的应用程序而言非常的happy,直接就能使用各种现成的算法

    5.自定义的线程互斥锁与GIL的区别

             GIL保护的是解释器级别的数据安全,比如对象的引用计数,垃圾分代数据等等,具体参考垃圾回收机制详解

             自定义的互斥锁是对程序中自己定义的数据进行加锁,,例如程序中共享自定义的数据的时候就要自己加锁

    6.线程池与进程池

            1)什么是进程/线程池

                       池表示一个容器,本质上就是一个存储进程或线程的列表

            2)线程池与进程池的使用环境

                       如果是IO密集型任务使用线程池,如果是计算密集任务则使用进程池

            3)为什么需要进程/线程池

                    在很多情况下需要控制进程或线程的数量在一个合理的范围,例如TCP程序中,一个客户端对应一个线程,虽然线程的开销小,但  肯定不能无限的开,否则系统资源迟早被耗尽,解决的办法就是控制线程的数量。线程/进程池不仅帮我们控制线程/进程的数量,还帮我们完成了线程/进程的创建,销毁,以及任务的分配

    7.同步异步,阻塞非阻塞

            塞非阻塞指的是程序的运行状态

           阻塞:当程序执行过程中遇到了IO操作,在执行IO操作时,程序无法继续执行其他代码,称为阻塞!

          非阻塞:程序在正常运行没有遇到IO操作,或者通过某种方式使程序即时遇到了也不会停在原地,还可以执行其他操作,以提高CPU的占用率

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

           同步指调用:发起任务后必须在原地等待任务执行完成,才能继续执行

           异步指调用:发起任务后必须不用等待任务执行,可以立即开启执行其他操作

           同步会有等待的效果但是这和阻塞是完全不同的,阻塞时程序会被剥夺CPU执行权,而同步调用则不会!

            很明显异步调用效率更高,但是任务的执行结果如何获取呢?程序中的异步调用并获取结果方式1:

    from concurrent.futures import ThreadPoolExecutor
    from threading import current_thread
    import time
    
    pool = ThreadPoolExecutor(3)
    def task(i):
        time.sleep(0.01)
        print(current_thread().name,"working..")
        return i ** i
    
    if __name__ == '__main__':
        objs = []
        for i in range(3):
            res_obj = pool.submit(task,i) # 异步方式提交任务# 会返回一个对象用于表示任务结果
            objs.append(res_obj)
    
    # 该函数默认是阻塞的 会等待池子中所有任务执行结束后执行
    pool.shutdown(wait=True)
    
    # 从结果对象中取出执行结果
    for res_obj in objs:
        print(res_obj.result())
    print("over")
    

      程序中的异步调用并获取结果方式2:

    from concurrent.futures import ThreadPoolExecutor
    from threading import current_thread
    import time
    
    pool = ThreadPoolExecutor(3)
    def task(i):
        time.sleep(0.01)
        print(current_thread().name,"working..")
        return i ** i
    
    if __name__ == '__main__':
        objs = []
        for i in range(3):
            res_obj = pool.submit(task,i) # 会返回一个对象用于表示任务结果
            print(res_obj.result()) #result是同步的一旦调用就必须等待 任务执行完成拿到结果
    print("over")
    

      

    8.异步回调

        什么是异步回调

             异步回调指的是:在发起一个异步任务的同时指定一个函数,在异步任务完成时会自动的调用这个函数

         为什么需要异步回调

            之前在使用线程池或进程池提交任务时,如果想要处理任务的执行结果则必须调用result函数或是shutdown函数,而它们都是是阻的,会等到任务执行完毕后才能继续执行,这样一来在这个等待过程中就无法执行其他任务,降低了效率,所以需要一种方案,即保证解析果的线程不用等待,又能保证数据能够及时被解析,该方案就是异步回调

          异步回调的使

           先来看一个案例:

           在编写爬虫程序时,通常都是两个步骤:

          1.从服务器下载一个网页文件

          2.读取并且解析文件内容,提取有用的数据

           按照以上流程可以编写一个简单的爬虫程序

           要请求网页数据则需要使用到第三方的请求库requests可以通过pip或是pycharm来安装,在pycharm中点击settings->解释器->点击+号-> 搜索requests->安装

       

    import requests,re,os,random,time
    from concurrent.futures import ProcessPoolExecutor
    
    def get_data(url):
        print("%s 正在请求%s" % (os.getpid(),url))
        time.sleep(random.randint(1,2))
        response = requests.get(url)
        print(os.getpid(),"请求成功 数据长度",len(response.content))
        #parser(response) # 3.直接调用解析方法  哪个进程请求完成就那个进程解析数据  强行使两个操作耦合到一起了
        return response
    
    def parser(obj):
        data = obj.result()
        htm = data.content.decode("utf-8")
        ls = re.findall("href=.*?com",htm)
        print(os.getpid(),"解析成功",len(ls),"个链接")
    
    if __name__ == '__main__':
        pool = ProcessPoolExecutor(3)
        urls = ["https://www.baidu.com",
                "https://www.sina.com",
                "https://www.python.org",
                "https://www.tmall.com",
                "https://www.mysql.com",
                "https://www.apple.com.cn"]
        # objs = []
        for url in urls:
            # res = pool.submit(get_data,url).result() # 1.同步的方式获取结果 将导致所有请求任务不能并发
            # parser(res)
    
            obj = pool.submit(get_data,url) # 
            obj.add_done_callback(parser) # 4.使用异步回调,保证了数据可以被及时处理,并且请求和解析解开了耦合
            # objs.append(obj)
            
        # pool.shutdown() # 2.等待所有任务执行结束在统一的解析
        # for obj in objs:
        #     res = obj.result()
        #     parser(res)
        # 1.请求任务可以并发 但是结果不能被及时解析 必须等所有请求完成才能解析
        # 2.解析任务变成了串行
    

      

    总结:异步回调使用方法就是在提交任务后得到一个Futures对象,调用对象的add_done_callback来指定一个回调函数,

    如果把任务比喻为烧水,没有回调时就只能守着水壶等待水开,有了回调相当于换了一个会响的水壶,烧水期间可用作其他的事情,等待水开了水壶会自动发出声音,这时候再回来处理。水壶自动发出声音就是回调。

    注意:

    1. 使用进程池时,回调函数都是主进程中执行执行

    2. 使用线程池时,回调函数的执行线程是不确定的,哪个线程空闲就交给哪个线程

    3. 回调函数默认接收一个参数就是这个任务对象自己,再通过对象的result函数来获取任务的处理结果

    9.线程队列

        1.Queue 先进先出队列

            与多进程中的Queue使用方式完全相同,区别仅仅是不能被多进程共享。     

        2.LifoQueue 后进先出队列

           该队列可以模拟堆栈,实现先进后出,后进先出

        3.PriorityQueue 优先级队列

            该队列可以为每个元素指定一个优先级,这个优先级可以是数字,字符串或其他类型,但是必须是可以比较大小的类型,取出数据时会按 照从小到大的顺序取出

     

    10.线程事件Event

             什么是事件

                事件表示在某个时间发生了某个事情的通知信号,用于线程间协同工作。

                因为不同线程之间是独立运行的状态不可预测,所以一个线程与另一个线程间的数据是不同步的,当一个线程需要利用另一个线程的状态来确定自己的下一步操作时,就必须保持线程间数据的同步,Event就可以实现线程间同步

             Event介绍

                  Event象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果 有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行

    event.isSet():返回event的状态值;
    event.wait():将阻塞线程;知道event的状态为True
    event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
    event.clear():恢复event的状态值为False。
    

      使用案例:

    # 在链接mysql服务器前必须保证mysql已经启动,而启动需要花费一些时间,所以客户端不能立即发起链接 需要等待msyql启动完成后立即发起链接
    from threading import Event,Thread
    import time
    
    boot = False
    def start():
        global boot
        print("正正在启动服务器.....")
        time.sleep(5)
        print("服务器启动完成!")
        boot = True
        
    def connect():
        while True:
            if boot:
                print("链接成功")
                break
            else:
                print("链接失败")
            time.sleep(1)
    
    Thread(target=start).start()
    Thread(target=connect).start()
    Thread(target=connect).start()
    

      使用Event改造后

    from threading import Event,Thread
    import time
    
    e = Event()
    def start():
        global boot
        print("正正在启动服务器.....")
        time.sleep(3)
        print("服务器启动完成!")
        e.set()
    
    def connect():
        e.wait()
        print("链接成功")
        
    Thread(target=start).start()
    Thread(target=connect).start()
    Thread(target=connect).start()
    

      增加需求,每次尝试链接等待1秒,尝试次数为3次

    from threading import Event,Thread
    import time
    
    e = Event()
    def start():
        global boot
        print("正正在启动服务器.....")
        time.sleep(5)
        print("服务器启动完成!")
        e.set()
    
    def connect():
        for i in range(1,4):
            print("第%s次尝试链接" % i)
            e.wait(1)
            if e.isSet():
                print("链接成功")
                break
            else:
                print("第%s次链接失败" % i)
        else:
            print("服务器未启动!")
    
    Thread(target=start).start()
    Thread(target=connect).start()
    # Thread(target=connect).start()
    

      

     

  • 相关阅读:
    37.Spring-事务控制.md
    35.Spring-jdbc支持.md
    29.Hibernate-二级缓存和session管理.md
    28.Hibernate-HQL查询.md
    27.Hibernate-缓存和懒加载.md
    26.Hibernate-主键和映射.md
    25.Hibernate-配置文件.md
    24.Hibernate-各种类型的查询.md
    MarkDown学习记录
    gitbook使用
  • 原文地址:https://www.cnblogs.com/xzcvblogs/p/10981522.html
Copyright © 2020-2023  润新知