• Python -- 协程


    9.4 协程

    9.4.1 协程初识

    1. 回顾串行,并行,并发

      串行:多个任务执行时,第一个任务开始执行,遇到IO阻塞就等待,IO阻塞结束之后,继续执行下个任务

      并行:多核,多个形成或者进程同时进行,4个CPU同时执行四个任务

      并发:多个任务看起来同时执行,实质是CPU在多个任务之间来回切换(遇到IO阻塞,计算密集型执行时间过长)

      并发的本质:

      1. 遇到IO阻塞,计算密集型执行时间过长,切换
      2. 保持线程或进程原来的状态
    2. 协程与多进程多线程比较

      多进程:操作系统控制多个进程的多个任务切换+保持状态

      多线程:操作系统控制多个线程的多个任务切换+保持状态

      协程:程序控制一个线程的多个任务的切换以及保持状态

      协程属于微并发,处理任务不易过多.

      协程会调度CPU,如果协程管控的任务中,遇到阻塞,他会快速的(比操作系统快)切换到另一个任务,并且能将上一个任务挂机(保持状态),让操作系统以为CPU一直在工作.

    3. 协程基础知识

      协程是单线程下的并发,又称为线程,纤程

      什么是协程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的.

      需要强调的是:

      1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
      2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
      

    对比操作系统控制线程的切换,用户在单线程内控制协程的切换

    优点如下:

    1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
    2. 单线程内就可以实现并发的效果,最大限度地利用cpu
    

    缺点如下:

    1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
    2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
    

    总结协程特点:

    1. 必须在只有一个单线程里实现并发
    2. 修改共享数据不需加锁(优点)
    3. 用户程序里自己保存多个控制流的上下文栈
    4. 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

    9.4.2 协程

    1. 计算密集型:串行与协程的效率对比
    import time
    
    def task1():
        res = 1
        for i in range(1,100000):
            res += i
    
    def task2():
        res = 1
        for i in range(1,100000):
            res -= i
    
    start_time = time.time()
    task1()
    task2()
    print(f'串行消耗时间:{time.time()-start_time}')0.01754593849182129
    
    import time
    def task1():
        res = 1
        for i in range(1, 100000):
            res += i
            yield res
    def task2():
        g = task1()
        res = 1
        for i in range(1, 100000):
            res -= i
            next(g)
    start_time = time.time()
    task2()
    print(f'协程消耗时间:{time.time() - start_time}')0.023561477661132812
    
    1. 协程:遇到IO自动切换
    from greenlet import greenlet
    import time
    
    # 不能自动切换,
    # 遇到IO不切换
    # 可以保持原来的状态.
    def eat(name):
    
        print('%s eat 1' %name)  #2
        g2.switch('alex')   #3
        time.sleep(3)
        print('%s eat 2' %name) #6
        g2.switch() #7
    
    def play(name):
        print('%s play 3' %name) #4
        g1.switch()      #5
        print('%s play 4' %name) #8
    
    g1 = greenlet(eat)
    g2 = greenlet(play)
    
    g1.switch('太白')  # 1  第一次切换一定要传参
    
    g2.switch('b1')
    
    time.sleep(300)
    
    # 还没有做到真正遇到IO切换
    
    import gevent
    import time
    def eat(name):
        print('%s eat 1' %name)  # 1
        # gevent.sleep(2)
        time.sleep(3)
        print('%s eat 2' %name)
    
    def play(name):
        print('%s play 1' %name)  # 2
        # gevent.sleep(1)
        time.sleep(2)
        print('%s play 2' %name)
    
    
    g1 = gevent.spawn(eat, 'alex')
    g2 = gevent.spawn(play, name='taibai')
    g1.join()
    g2.join()
    # 或者gevent.joinall([g1,g2])
    gevent.joinall([g1,g2])
    print('主')
    
    import threading
    from gevent import monkey
    monkey.patch_all()  # 将你代码中的所有的IO都标识.
    
    import gevent  # 直接导入即可
    import time
    def eat():
        print(f'线程1:{threading.current_thread().getName()}')
        print('eat food 1')
        time.sleep(3)  # 加上mokey就能够识别到time模块的sleep了
        print('eat food 2')
    
    def play():
        print(f'线程2:{threading.current_thread().getName()}')
        print('play 1')
        time.sleep(1)  # 来回切换,直到一个I/O的时间结束,这里都是我们个gevent做得,不再是控制不了的操作系统了。
        print('play 2')
    
    g1=gevent.spawn(eat)
    g2=gevent.spawn(play)
    gevent.joinall([g1,g2])
    print(f'主:{threading.current_thread().getName()}')
    
  • 相关阅读:
    atcoder #082 E 暴力 计算几何
    LightOJ 1364 树形DP
    gym100712 ACM Amman Collegiate Programming Contest
    CF757 C hash
    CF844 C 置换 水
    CF544 C 背包 DP
    CF540 D 概率 DP
    CF540 C BFS 水
    CF540 B 贪心
    CF745 C 并查集
  • 原文地址:https://www.cnblogs.com/Agoni-7/p/11295873.html
Copyright © 2020-2023  润新知