zoukankan      html  css  js  c++  java
  • Python协程

     一、一些基本概念:

    协程(Coroutine),又称微线程,纤程,一种用户级的轻量级线程。

    栈(Stack)是一个数据集合,可以理解为只能在一端进行插入或删除操作的列表。

    协程拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态。

    在并发编程中,协程与线程类似,每个协程表示一个执行单元,有自己的本地数据,与其他协程共享全局数据和其他资源

    协程需要用户自己来编写调度逻辑,对于CPU来说,协程其实是单线程,所以cpu不用去考虑怎么调度,切换上下文,这就省去了cpu的切换开销,所以协程一定程度上又好于多线程

    协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。

    协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程;

    二、yield回顾:

    def f():
        print('ok1')
        count=yield 5
        print(count)
        print('ok2')
        yield 6
    
    gen=f()
    # ret=next(gen)
    # print(ret)
    ret=gen.send(None)
    print(ret)
    ret2=gen.send(7) #7赋值给count。
    print(ret2)
    
    output:
    ok1
    5
    7
    ok2
    6
    #__author: greg1617
    #date: 2017/9/22 9:21
    
    def consumer(name):
        print("--->starting eating baozi...")
        while True:
            new_baozi = yield
            print("[%s] is eating baozi %s" % (name, new_baozi))
            # time.sleep(1)
    
    def producer():
        r = con.__next__()#  等于next(con)
        r = con2.__next__()#  等于next(con2)
        n = 0
        while n < 5:
            n += 1
            con.send(n)
            con2.send(n)
            print("33[32;1m[producer]33[0m is making baozi %s" % n)
    
    if __name__ == '__main__':
        con = consumer("c1")# 创建一个生成器对象
        con2 = consumer("c2") #又创建一个
        p = producer() #执行producer函数,p是返回值
    吃包子

    三 gevent

    Gevent 是一个基于协程的Python网络函数库,可以轻松通过gevent实现并发同步或异步编程,对协程的支持,本质上是greenlet在实现切换工作。

    在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。
    Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

    greenlet工作流程:加入进行访问网络的IO操作时,出现阻塞,greenlet就显式切换到另一段没有被阻塞的代码段执行,知道原先的阻塞状况消失以后,再自动切换回原来的代码段继续处理。因此,greenlet是一种合理安排的串行方式。
    由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换携程,就保证总有greenlet在运行,而不是等待IO,这就是协程一般比多线程效率高的原因。

    由于切换实在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,将一些常见的阻塞,如socket、select等地方实现协程跳转,这一过后才能在启动时通过monkey patch完成。

    #__author: greg
    #date: 2017/9/22 11:06
    import gevent,time
    def func1():
        print('33[31;1mAAAAAAAAAA...33[0m',time.ctime())
        gevent.sleep(2) #遇到阻塞,IO操作,自动切换
        print('33[31;1mBBBBBBBBBB...33[0m',time.ctime())
    
    def func2():
        print('33[32;1mCCCCCCCCCCC...33[0m',time.ctime())
        gevent.sleep(1)
        print('33[32;1mDDDDDDDDDDDDDDDDD...33[0m',time.ctime())
    
    gevent.joinall([
        gevent.spawn(func1),
        gevent.spawn(func2),
    ])

    AAAAAAAAAA... Sat Nov 25 11:41:03 2017
    CCCCCCCCCCC... Sat Nov 25 11:41:03 2017
    DDDDDDDDDDDDDDDDD... Sat Nov 25 11:41:04 2017
    BBBBBBBBBB... Sat Nov 25 11:41:05 2017

    SPAWN方法可以看做用来形成协程,joinall方法就是添加这些协程任务,并且启动运行。

    四 greenlet

    #__author: greg1617
    #date: 2017/9/22 10:58
    from greenlet import greenlet
    #greenlet是一个用C实现的协程模块,相比与python自带的yield,
    # 它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator
    def test1():
        print(1)
        gr2.switch()
        print(2)
        gr2.switch()
    
    def test2():
        print(3)
        gr1.switch()
        print(4)
    
    gr1 = greenlet(test1)
    # print(gr1) #<greenlet.greenlet object at 0x00000196A3AC33D8>
    gr2 = greenlet(test2)
    gr1.switch()
    # 感觉确实用着比generator还简单了呢,但好像还没有解决一个问题,
    # 就是遇到IO操作,自动切换,对不对?

    同步和异步的比较

    #__author: greg
    #date: 2017/9/22 11:12
    import gevent,time
    start=time.time()
    def task(pid):
        """
        Some non-deterministic task
        """
        gevent.sleep(0.5)
        print('Task %s done' % pid)
    
    def synchronous():
        for i in range(1, 10):
            task(i)
    
    def asynchronous():
        threads = [gevent.spawn(task, i) for i in range(10)]
        gevent.joinall(threads)
    
    # print('Synchronous:')
    # synchronous()
    #按顺序执行,时间4.510729074478149
    
    
    # print('Asynchronous:')
    # asynchronous()
    #争先抢后的执行,时间0.5047242641448975
    
    end=time.time()
    print(end-start)

    遇到IO操作自动切换

    #__author: greg1617
    #date: 2017/9/22 11:29
    from gevent import monkey
    import time
    monkey.patch_all()
    import gevent
    from  urllib.request import urlopen
    
    def f(url):
        print('GET: %s' % url)
        resp = urlopen(url)
        data = resp.read()
        print('%d bytes received from %s.' % (len(data), url))
    
    start=time.time()
    
    # l=['https://www.python.org/','https://www.yahoo.com/','https://github.com/']
    # for url in l:
    #     f(url)
    
    #output:
    # GET: https://www.python.org/
    # 48847 bytes received from https://www.python.org/.
    # GET: https://www.yahoo.com/
    # 510810 bytes received from https://www.yahoo.com/.
    # GET: https://github.com/
    # 51383 bytes received from https://github.com/.
    # 4.774596929550171
    
    gevent.joinall([
        gevent.spawn(f, 'https://www.python.org/'),
        gevent.spawn(f, 'https://www.yahoo.com/'),
        gevent.spawn(f, 'https://github.com/'),
    ])
    #output:
    # GET: https://www.python.org/
    # GET: https://www.yahoo.com/
    # GET: https://github.com/
    # 48847 bytes received from https://www.python.org/.
    # 513713 bytes received from https://www.yahoo.com/.
    # 51383 bytes received from https://github.com/.
    # 3.432187795639038
    print(time.time()-start)

    五 gevent池

    在处理大量网络和IO操作时,进行并发管理,可以使用池

    # -*- coding: utf-8 -*-
    # 2017/11/25 11:58
    from gevent import monkey
    monkey.patch_all()
    import urllib.request
    from gevent.pool import Pool
    def run_task(url):
        print('Visit --> %s' % url)
        try:
            response = urllib.request.urlopen(url)
            data = response.read()
            print('%d bytes received from %s.' % (len(data), url))
        except Exception as e:
            print(e)
        return 'url:%s --->finish'% url
    
    if __name__=='__main__':
        pool = Pool(2)
        urls = ['https://github.com/','https://www.python.org/','http://www.cnblogs.com/']
        results = pool.map(run_task,urls)
        print(results)

    六 通过gevent实现单线程下的多socket并发

    服务端:

    #__author: greg
    #date: 2017/9/22 19:43
    # 通过gevent实现单线程下的多socket并发
    import sys
    import socket
    import time
    import gevent
    from gevent import socket, monkey
    
    monkey.patch_all()
    
    def server(port):
        s = socket.socket()
        s.bind(('0.0.0.0', port))
        s.listen(500)
        while True:
            cli, addr = s.accept()
            gevent.spawn(handle_request, cli)
    
    def handle_request(conn):
        try:
            while True:
                data = conn.recv(1024)
                print("recv:", data)
                conn.send(data)
                if not data:
                    conn.shutdown(socket.SHUT_WR)
    
        except Exception as  ex:
            print(ex)
        finally:
            conn.close()
    
    if __name__ == '__main__':
        server(8001)

    客户端

    #__author: greg
    #date: 2017/9/22 19:44
    import socket
    HOST = 'localhost'  # The remote host
    PORT = 8001  # The same port as used by the server
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))
    while True:
        msg = bytes(input(">>:"), encoding="utf8")
        s.sendall(msg)
        data = s.recv(1024)
        # print(data)
        print('Received', repr(data))
    s.close()
  • 相关阅读:
    【Hello CC.NET】巧用模板简化配置
    【Hello CC.NET】自动化发布时 Web.config 文件维护
    Hello Jexus
    【Hello CC.NET】CC.NET 实现自动化集成
    SSE和WebSocket的用法和比较
    利用canvas实现鼠标跟随效果
    使用es6制作简单数独游戏
    ppt学习(3)
    ppt学习(2)
    ppt学习(1)
  • 原文地址:https://www.cnblogs.com/ningxin18/p/7894922.html
Copyright © 2011-2022 走看看