• Python


    一、简介

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

    需要强调的是:

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

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

    优点如下:

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

    缺点如下:

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

    总结协程特点:

             1.必须在只有一个单线程里实现并发

             2.修改共享数据不需要加锁

             3.用户程序里自己保存多个控制流的上下文栈

    附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield,greenlet都无法实现,就用到了gevent模块(select机制))

    二、Greenlet

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

    安装:pip install greenlet

    #! /usr/bin/env python3
    # -*- coding:utf-8 -*-
    
    # Author   : mayi
    # Blog     : http://www.cnblogs.com/mayi0312/
    # Date     : 2019/6/3
    # Software : PyCharm
    
    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的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度

    #! /usr/bin/env python3
    # -*- coding:utf-8 -*-
    
    # Author   : mayi
    # Blog     : http://www.cnblogs.com/mayi0312/
    # Date     : 2019/6/3
    # Software : PyCharm
    
    # 顺序执行
    import time
    
    
    def f1():
        res = 1
        for i in range(100000000):
            res += i
    
    
    def f2():
        res = 1
        for i in range(100000000):
            res *= i
    
    
    start = time.time()
    f1()
    f2()
    stop = time.time()
    print('run time is %s' % (stop - start))  # 15.185868501663208
    
    print("------------------------------------------------")
    
    # 切换
    from greenlet import greenlet
    import time
    
    
    def f1():
        res = 1
        for i in range(100000000):
            res += i
            g2.switch()
    
    
    def f2():
        res = 1
        for i in range(100000000):
            res *= i
            g1.switch()
    
    
    start = time.time()
    g1 = greenlet(f1)
    g2 = greenlet(f2)
    g1.switch()
    stop = time.time()
    print('run time is %s' % (stop - start))  # 64.25667524337769

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

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

    三、Gevent模块

    安装:pip install gevent

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

    #! /usr/bin/env python3
    # -*- coding:utf-8 -*-
    
    # Author   : mayi
    # Blog     : http://www.cnblogs.com/mayi0312/
    # Date     : 2019/6/3
    # Software : PyCharm
    
    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('')

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

    上例gevent.sleep(2)模拟的是gevent可以识别的阻塞,而time.sleep(2)或其它的阻塞,gevent是不能直接识别的,需要用下面一行代码,打补丁才可以识别

    from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socke模块导入之前,或者干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头

    #! /usr/bin/env python3
    # -*- coding:utf-8 -*-
    
    # Author   : mayi
    # Blog     : http://www.cnblogs.com/mayi0312/
    # Date     : 2019/6/3
    # Software : PyCharm
    
    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)
    gevent.joinall([g1, g2])
    print('')

    四、Gevent之同步与异步

    #! /usr/bin/env python3
    # -*- coding:utf-8 -*-
    
    # Author   : mayi
    # Blog     : http://www.cnblogs.com/mayi0312/
    # Date     : 2019/6/3
    # Software : PyCharm
    
    from gevent import spawn, joinall, monkey;monkey.patch_all()
    import time
    
    
    def task(pid):
        """
        Some non-deterministic task
        """
        time.sleep(0.5)
        print('Task %s done' % pid)
    
    
    def synchronous():
        for i in range(10):
            task(i)
    
    
    def asynchronous():
        g_l = [spawn(task, i) for i in range(10)]
        joinall(g_l)
    
    
    if __name__ == '__main__':
        print('Synchronous:')
        synchronous()
    
        print('Asynchronous:')
        asynchronous()
        # 上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。初始
        # 化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall()函数,后
        # 者阻塞当前流程,并执行所有给定的greenlet。执行流程只会在所有greenlet执行
        # 完后才会继续向下走。

    五、Gevent之应用举例

    #! /usr/bin/env python3
    # -*- coding:utf-8 -*-
    
    # Author   : mayi
    # Blog     : http://www.cnblogs.com/mayi0312/
    # Date     : 2019/6/3
    # Software : PyCharm
    # Note     : 协程应用:爬虫
    
    from gevent import monkey;monkey.patch_all()
    import gevent
    import requests
    import time
    
    
    def get_page(url):
        print('GET: %s' % url)
        response = requests.get(url)
        if response.status_code == 200:
            print('%d bytes received from %s' % (len(response.text), url))
    
    
    start_time = time.time()
    gevent.joinall([
        gevent.spawn(get_page, 'https://www.python.org/'),
        gevent.spawn(get_page, 'https://www.yahoo.com/'),
        gevent.spawn(get_page, 'https://github.com/'),
    ])
    stop_time = time.time()
    print('run time is %s' % (stop_time - start_time))
  • 相关阅读:
    详解股票买卖算法的最优解(一)
    Broker的主从架构是怎么实现的?
    和同事谈谈Flood Fill 算法
    聊一聊RocketMQ的注册中心NameServer
    你懂RocketMQ 的架构原理吗?
    常见的消息中间件有哪些?你们是怎么进行技术选型的?
    什么是消息中间件?主要作用是什么?
    @staticmethod
    Pandas 简介
    pytorch 不同版本对应的cuda
  • 原文地址:https://www.cnblogs.com/mayi0312/p/12394279.html
Copyright © 2020-2023  润新知