• 协程


    一.同步异步

      线程的三种状态:

        1.就绪

        2.运行

        3.阻塞

      阻塞 遇到了IO操作 代码卡住 无法执行 CPU会切换到其他任务

      非阻塞 与阻塞相反 代码正在执行(运行状态)或处于就绪状态

      阻塞和非阻塞秒回的是运行的状态

      同步:提交任务必须等待任务完成,才能执行下一行

      异步:提交任务不需要等待任务完成,立即执行下一行

      指的是一种提交任务方式

    def task():
        for I in range(10000):
            I+=1000
        print("111111")
    
    print("start")
    task()#同步提交
    print("en")
    
    form threading import Thread
    
    print("start1")
    Thread(target=task).start()#异步提交
    print("end")

    二.异步回调

      为什么需要异步回调

        子进程帮助主进程完成任务 处理任务的结果应该还给主进程

      其他方式也可以将数据交给主进程

        1.shutdown 主进程会叨叨所有任务完成

        2.result函数 会阻塞知道任务完成

      注意:

        回调函数什么时候执行?子进程任务完成时

        谁在执行回调函数?主进程

      线程的异步回调

        使用方法相同 唯一不同是线程没有主线程,都是子线程执行

    三.生产者消费者

    from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
    from threading import current_thread
    pool = ThreadPoolExecutor()
    
    import requests
    
    def get_data_task(url):
        print(current_thread(),"正在生产数据!|")
    
        response =requests.get(url)
        text = response.content.decode("utf-8")
        print(text)
        return text
    
    def parser_data(f):
        print(current_thread(),"处理数据")
        print("正在解析:长度%s"%len(f.result()))
    
    urls=[
        "http://www.baidu.con",
        "http://www.taobao.con",
        "http://www.jindong.con",
        "http://www.4399.con"
    ]
    if __name__=='__main__':
        for url in urls:
            f = pool.submit(get_data_task,url)
            f.add_done_callback(parser_data)
            print("over")

    四.线程队列

    import queue
    
    # 普通队列 先进先出
    q = queue.Queue()
    q.put("a")
    q.put("b")
    
    
    print(q.get())
    print(q.get())
    
    # 堆栈队列  先进后出 后进先出  函数调用就是进栈  函数结束就出栈 递归造成栈溢出
    q2 = queue.LifoQueue()
    q2.put("a")
    q2.put("b")
    print(q2.get())
    
    
    # 优先级队列
    q3 = queue.PriorityQueue()  # 数值越小优先级越高  优先级相同时 比较大小 小的先取
    q3.put((-100,"c"))
    q3.put((1,"a"))
    q3.put((100,"b"))
    print(q3.get())

    五.协程

      协程的目的是在单线程下实现并发
      为什么出现协程? 因为cpython 由于GIL 导致同一时间只有一个线程再跑
        意味着 如果你的程序时计算密集 多线程效率也不会提升
        如果是io密集型 有没有必要再单线程下实现并发
        没有 我会开启多线程来处理io 子线遇到io cpu切走 但是请问 你能保证一定切到主线吗? 不能保证
        有 如果可以 我在遇到io的时候转而去做计算 这样一来可以保证cpu一直在处理你的程序 当然时间太长也要切走

        总结一下:单线下实现并发 将io阻塞时间用于执行计算 可以提高效率 原理:一直使用CPU直到超时
      怎么实现单线程并发?
        并发 指的是 看起来像是同时运行 实际是在任务间来回切换 同时需要保存执行的状态
        任务一堆代码 可以用函数装起来
        1.如何让两个函数切换执行
          yield可以保存函数的执行状态
          通过生成器可以实现伪并发
          并发不一定提升效率 反而会降低效率 当任务全是计算时
        2.如何知道发生了io? 从而切换执行
          目前咱们实现不了
      第三方模块 greenlet 可以实现并发 但是不能检测io
      第三方模块 gevent 封装greenlet 可以实现单线程并发 并且能够检测io操作 自动切换

      协程的应用场景:
        TCP 多客户端实现方式
        1.来一个客户端就来一个进程 资源消耗较大
        2.来一个客户端就来一个线程 也不能无限开
        3.用进程池 或 线程池 还是一个线程或进程只能维护一个连接
        4.协程 一个线程就可以处理多个客户端 遇到io就切到另一个

     六 greenlet

    如果我们在单个线程内有20个任务,要想实现在多个任务之间切换,使用yield生成器的方式过于麻烦(需要先得到初始化一次的生成器,然后再调用send。。。非常麻烦),而使用greenlet模块可以非常简单地实现这20个任务直接的切换

    from greenlet import greenlet
    
    def eat(name):
        print('%s eat 1' %name)
        g2.switch('egon')
        print('%s eat 2' %name)
        g2.switch()
    def play(name):
        print('%s play 1' %name)
        g1.switch()
        print('%s play 2' %name)
    
    g1=greenlet(eat)
    g2=greenlet(play)
    
    g1.switch('egon')#可以在第一次switch时传入参数,以后都不需要

    单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度

    greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。

    单线程里的这20个任务的代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。。。。如此,才能提高效率,这就用到了Gevent模块。

    七 gevent

    Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

    g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的
    
    g2=gevent.spawn(func2)
    
    g1.join() #等待g1结束
    
    g2.join() #等待g2结束
    
    #或者上述两步合作一步:gevent.joinall([g1,g2])
    
    g1.value#拿到func1的返回值

    遇到IO阻塞时会自动切换任务

    import gevent
    def eat(name):
        print('%s eat 1' %name)
        gevent.sleep(2)
        print('%s eat 2' %name)
    
    def play(name):
        print('%s play 1' %name)
        gevent.sleep(1)
        print('%s play 2' %name)
    
    
    g1=gevent.spawn(eat,'egon')
    g2=gevent.spawn(play,name='egon')
    g1.join()
    g2.join()
    #或者gevent.joinall([g1,g2])
    print('')

    上例gevent.sleep(2)模拟的是gevent可以识别的io阻塞,

    而time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了

    from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前

    或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头

     

    from gevent import monkey;monkey.patch_all()

    import gevent
    import time
    def eat():
    print('eat food 1')
    time.sleep(2)
    print('eat food 2')

    def play():
    print('play 1')
    time.sleep(1)
    print('play 2')

    g1=gevent.spawn(eat)
    g2=gevent.spawn(play_phone)
    gevent.joinall([g1,g2])
    print('主')

    什么是协程

    协程是是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
    为什么需要协程

    因为线程是从内核级别进行调度,当某个线程遇到IO或执行时间过长时,操作系统会强制切换到其他线程

    而在单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率


    协程的使用场景


    TCP 多客户端实现方式
    1.来一个客户端就来一个进程 资源消耗较大
    2.来一个客户端就来一个线程 也不能无限开
    3.用进程池 或 线程池 还是一个线程或进程只能维护一个连接
    4.协程 一个线程就可以处理多个客户端 遇到io就切到另一个


    2.使用协程完成TCP套接字编程 支持多客户端同时访问


    3.什么是异步 什么是异步回调 为什么需要异步回调

    异步 :提交任务不需要等待任务完成,立即执行下一行

    子进程帮助主进程完成任务 处理任务额结果应该还给主进程

    提高效率,让每一个线程都工做起来.

  • 相关阅读:
    UltraEdit程序设置添加到右键菜单
    UltraEdit加入到右键菜单中
    vim常用命令
    Linux vi命令大全
    vi/vim 计算搜寻关键字数量
    vi 删除全部内容
    vi中全选的命令或者快捷方式
    VI打开和编辑多个文件的命令
    vi/vim 查找替换使用方法
    VMware workstation 与 VMware GSX Server 的区别
  • 原文地址:https://www.cnblogs.com/gongcheng-/p/9954895.html
Copyright © 2020-2023  润新知