zoukankan      html  css  js  c++  java
  • 并发编程之协程

    协程---协助线程更高效的工作。本质就是单线程实现并发,也称之为微线程(比线程更轻量级。单线程下任务切换,比操作系统切换线程简单)

    为将单线程的效率最大化。本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。

     

    那么做到协程起码得实现以下两点:

    1. 可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。
    
    2. 作为1的补充:可以检测IO操作,在遇到io操作的情况下才发生切换

    那如何去实现呢?

    对于 1 . 可以使用yield  但是yield 不能识别阻塞

    import time
    
    def func1():
        while True:
            yield
    
    def func2():
        g=func1()
        for i in range(10000000):
            i+1
            next(g)
    
    start=time.time()
    func2()
    stop=time.time()
    print(stop-start)
    yield

    生成器:特点---只要带yield的函数

    def test1():
        print(1)
        print(2)
        print(3)
    print(test1())
    # 使用生成器 实现单线程并发
    import time
    def task1():
        a = 1
        while True:
            print("task1 run")
            a += 1
            print(a)
            yield
    
    def task2():
        g = task1()
        while True:
            print("task2 run")
            time.sleep(10)
            next(g)
    task2()
    View Code

    对于计算密集型,单线程并发反而降低效率

    对于IO密集型 若可以在执行IO时就直接切换到 其他计算任务---提高CPU占用率----》提高效率

     

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

    而使用greenlet模块可以非常简单地实现这20个任务直接的切换(这是个第三方模块,需要自己安装)

    greenlet

    使用时需先在系统中下载(gevent也需要)

    封装了生成器 使得我们在使用生成器实现并发时 简化了代码

    import greenlet
    import time
    
    def task1():
        print("task1 run")
        time.sleep(10)
        g2.switch()
        print("task1 run")
    
    def task2():
        print("task2 run")
        g1.switch()
    
    g1 = greenlet.greenlet(task1)
    g2 = greenlet.greenlet(task2)
    g1.switch()
    View Code

     但是以上两者都不能实现识别到阻塞,并在阻塞前切换到

    import gevent, time
    from gevent import monkey  # 帮助监测程序自己的阻塞
    
    
    def micro_thread1():
        print("thread1 run...")
        print(time.time())
        # gevent.sleep(2)# 可以检测到自己的阻塞
        monkey.patch_all()
        print("睡眠")
        time.sleep(1)  # 你以为你用的sleep 实际上间接用的 gevent里的sleep  点patch_all()进去看
        print("thread1 run...")
    
    
    def micro_thread2():
        print(time.time())
        time.sleep(3)  # 模拟IO操作耗时   在IO的时候  micro_thread1 还在运行
        print("thread2 run...")
    
    
    g1 = gevent.spawn(micro_thread1)
    g2 = gevent.spawn(micro_thread2)
    
    gevent.joinall([g1, g2])  # 以后就都这样写 便不会因为单独对象join()而出错 这里的join兼顾start() 而单独的start是没用的
    gevent 与 monkey
    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))
    在爬虫中的实例

     我们可以用threading.current_thread().getName()来查看每个g1和g2,查看的结果为DummyThread-n,即假线程

     ”gevent.spawn()”方法会创建一个新的greenlet协程对象,并运行它。”gevent.joinall()”方法会等待所有传入的greenlet协程运行结束后再退出,这个方法可以接受一个”timeout”参数来设置超时时间,单位是秒。

    greenlet一个协程运行完后,必须显式切换,不然会返回其父协程。而在gevent中,一个协程运行完后,它会自动调度那些未完成的协程。

    Gevent

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

    python中,用gevent模块来实现协程,其能在多个任务间进行切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。且自己检测IO,在遇到io操作的情况下才发生切换

    #用法
    g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的
    from gevent import monkey
    monkey.patch_all()
    
    import gevent
    import time
    def task1():
        print("task1 run")
        time.sleep(10)
        print("task1 run")
    
    def task2():
        print("task2 run")
        print("task2 run")
    
    g1 = gevent.spawn(task1)
    g2 = gevent.spawn(task2)
    
    # g1.join()
    # g2.join()
    # 等待所有任务结束  注意:如果开启了一个会产生的io的任务  并且你没有执行join,
    # 那么一旦发生io 这个任务就立马结束了
    gevent.joinall([g2,g1])
    View Code

    猴子补丁 Monkey patching

    细心的朋友们会想到Python标准库里的socket是阻塞式的,DNS解析无法并发,包括像urllib库也一样,所以这种情况下用这种协程完全没意义。那怎么办?

    一种方法是使用gevent下的socket模块,我们可以通过”from gevent import socket”来导入。不过更常用的方法是使用猴子布丁(Monkey patching)

    from gevent import monkey
    monkey.patch_socket()
    #coding:utf8
    import gevent
    from gevent.event import Event
     
    evt = Event()
     
    def setter():
        print 'Wait for me'
        gevent.sleep(3)  # 3秒后唤醒所有在evt上等待的协程
        print "Ok, I'm done"
        evt.set()  # 唤醒
     
    def waiter():
        print "I'll wait for you"
        evt.wait()  # 等待
        print 'Finish waiting'
     
    gevent.joinall([
        gevent.spawn(setter),
        gevent.spawn(waiter),
        gevent.spawn(waiter),
        gevent.spawn(waiter),
        gevent.spawn(waiter),
        gevent.spawn(waiter)
    ])
    Event实现协程间通信

     除了Event事件外,gevent还提供了AsyncResult事件,它可以在唤醒时传递消息。让我们将上例中的setter和waiter作如下改动:

    from gevent.event import AsyncResult
    aevt = AsyncResult()
     
    def setter():
        print 'Wait for me'
        gevent.sleep(3)  # 3秒后唤醒所有在evt上等待的协程
        print "Ok, I'm done"
        aevt.set('Hello!')  # 唤醒,并传递消息
     
    def waiter():
        print("I'll wait for you")
        message = aevt.get()  # 等待,并在唤醒时获取消息
        print 'Got wake up message: %s' % message
    AsyncResult

    协程特点:

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

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

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

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

  • 相关阅读:
    java第四次作业
    java第五次作业
    java第三次作业
    第二次学习笔记
    java学习笔记
    第十四周完成情况
    课程计划进程
    课程设计分工
    JAVA学习笔记(六)
    JAVA学习笔记(五)
  • 原文地址:https://www.cnblogs.com/dongzhihaoya/p/10221949.html
Copyright © 2011-2022 走看看