• python网络编程-协程(协程说明,greenlet,gevent)


    一:什么是协程

      协程(Coroutine):,又称微线程。协程是一种用户态的轻量级线程。是由用户自己控制,CPU根本不知道协程存在。

      协程拥有自己的寄存器上下文和栈。

      协程调度切换时,将寄存器上下文和栈保存在其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈

      因此:协程能保留上一次调用的时的状态,每次过程重入时,就相当于进入上一次调用的。

      换种说法:进入上一次离开时所处逻辑流的位置。

      注意:线程切换会保存到CPU的寄存器里。

      协程的标准:

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

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

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

      4)一个协程遇到IO操作自动切换到其他协程

      

    二:协程在什么时候切换

      在什么时候进程切换:遇到I/O操作就切换,协程就是把io踢掉了(因为IO耗时)。

       什么时候切回去: I0操作调用完了,通过调用callback切换回去

    三:协程的优点缺点

      优点:

      1)无需线程上下文切换的开销

      2)无需原子操作锁定及同步的开销(因为协程就是单线程,它就是串行,同一时间改数据只有一个线程)

      3)方便切换控制流,简化编程模型

      4)高并发+高扩展性+低成本:一个CPU支持上万的协程不是问题,很适合高并发

      缺点:

      1)无法利用多核资源:协程本质是单线程,他不能同时单个CPU的多个核用上,协程需要和进程配合

        才能运行在多CPU上。

      2)进行阻塞(Blocking)操作(如IO时)会阻塞整个程序

    四:yield实现切换

      

    # -*- coding:utf-8 -*-
    __author__ = 'shisanjun'
    
    import time
    import queue
    
    def consumer(name):
        print("------->starting eating baozi...")
    
        while True:
            new_baozi=yield
            print("[%s] is eating baozi %s" %(name,new_baozi))
    
    def producer():
        r=con.__next__() #con=consumer("c1")#只是生成生成器,不会执行,所以先要调用next才会开始执行
        r=con2.__next__()
    
        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()
    
    """
    ------->starting eating baozi...
    ------->starting eating baozi...
    [c1] is eating baozi 1
    [c2] is eating baozi 1
    [producer] is making baozi 1
    [c1] is eating baozi 2
    [c2] is eating baozi 2
    [producer] is making baozi 2
    [c1] is eating baozi 3
    [c2] is eating baozi 3
    [producer] is making baozi 3
    [c1] is eating baozi 4
    [c2] is eating baozi 4
    [producer] is making baozi 4
    [c1] is eating baozi 5
    [c2] is eating baozi 5
    [producer] is making baozi 5
    """

      我们刚才用yield实现一个简单的协程,实现单线程多并发。

    五:Greenlet

      greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator

      

    # -*- coding:utf-8 -*-
    __author__ = 'shisanjun'
    
    from greenlet import greenlet
    
    def test1():
        print(12)
        gr2.switch()
        print(34)
        gr2.switch()
    
    def test2():
        print(56)
        gr1.switch()
        print(78)
    
    gr1=greenlet(test1)#起动一个协程
    gr2=greenlet(test2)
    gr1.switch() #从test1开始

      上面代码切换过程

      

       没有解决一个问题,就是遇到IO操作,自动切换,手动切换。下面实现自动切换

     六:Gevent 

       Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet,  

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

       

    # -*- coding:utf-8 -*-
    __author__ = 'shisanjun'
    
    import gevent
    
    def func1():
        print("33[31;1m 李在搞锗33[0m")
        gevent.sleep(2)#遇到sleep自动切换,模拟IO操作
        print("33[31;1m 李在又继续搞锗。。。。33[0m")
    
    def func2():
        print(("33[32;1m 李切换搞牛。。。33[0m"))
        gevent.sleep(1)#遇到sleep自动切换
        print(("33[32;1m 李切换继续搞牛。。。33[0m"))
    
    
    gevent.joinall(
        [
            gevent.spawn(func1),#可以带多个参数,第一个为函数名,第二个为函数参数
            gevent.spawn(func2)
        ]
    )
    
    """
     李在搞锗
     李切换搞牛。。。
     李切换继续搞牛。。。
     李在又继续搞锗。。。。
    """

     七: 同步与异步的性能区别

    # -*- coding:utf-8 -*-
    __author__ = 'shisanjun'
    
    import gevent
    
    def task(pid):
        gevent.sleep(0.5)
        print('Task %s done' %pid)
    
    def synchronous():
        for i in range(1,10):
            task(i)
    
    def asynchronous():
        theads=[gevent.spawn(task,i) for i in range(10)]
        gevent.joinall(theads)
    
    print("synchronous")
    synchronous()  #顺序执行,结果是一个个出来
    print("asynchronous")
    asynchronous() #并发执行,结果几乎同时出来
    
    """
    synchronous
    Task 1 done
    Task 2 done
    Task 3 done
    Task 4 done
    Task 5 done
    Task 6 done
    Task 7 done
    Task 8 done
    Task 9 done
    asynchronous
    Task 0 done
    Task 9 done
    Task 8 done
    Task 7 done
    Task 6 done
    Task 5 done
    Task 4 done
    Task 3 done
    Task 2 done
    Task 1 done
    
    """
    

      上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn

       初始化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall 函数,  

      后者阻塞当前流程,并执行所有给定的greenlet。执行流程只会在 所有greenlet执行完后才会继续向下走

       

     八:自动遇到IO切换

      

    Gevent 默认不知道urllib,socket做了IO操作,所以打补厅,增加monkey.patch_all()
    # -*- coding:utf-8 -*-
    __author__ = 'shisanjun'
    
    from gevent import monkey
    import gevent
    from urllib.request import urlopen
    #gevent默认检测不到ulrlib,所以默认是阻塞的,要加monkey实现自动切换
    monkey.patch_all()#实现遇到IO就自动切换
    
    def f(url):
        print('Get %s '%url)
        resp=urlopen(url)#这里自动切换了
    
        data=resp.read()
        print("%d bytes received from %s." %(len(data),data))
    
    gevent.joinall([
        gevent.spawn(f,"https://www.baidu.com"),
        gevent.spawn(f,"https://www.360.cn"),
    
    
    ])

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

      

    # -*- coding:utf-8 -*-
    __author__ = 'shisanjun'
    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(100)
    
        while True:
            conn,addr=s.accept()
            gevent.spawn(handle_request,conn)
    
    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 e:
            print(e)
        finally:
            conn.close()
    
    if __name__=="__main__":
        server(8001)
    View Code
    # -*- coding:utf-8 -*-
    __author__ = 'shisanjun'
    import socket
    import threading
    
    def sock_conn():
    
        client = socket.socket()
    
        client.connect(("localhost",8001))
        count = 0
        while True:
            #msg = input(">>:").strip()
            #if len(msg) == 0:continue
            client.send( ("hello %s" %count).encode("utf-8"))
    
            data = client.recv(1024)
    
            print("[%s]recv from server:" % threading.get_ident(),data.decode()) #结果
            count +=1
        client.close()
    
    
    for i in range(100):
        t = threading.Thread(target=sock_conn)
        t.start()
    
    #并发100个sock连接
    View Code

    本文没有解决:什么时候切换回来

  • 相关阅读:
    (转)超过 130 个你需要了解的 vim 命令
    ubuntu下解压文件命令大全(转)
    HDU 4681 String
    Linux使用过程中常见问题及其解决方法
    Linux 命令 及 简单操作 学习
    HDU 4666 Hyperspace (最远曼哈顿距离)
    POJ 2049 Finding Nemo
    HDU 4655 Cut Pieces
    <textarea>标签的使用
    数据库插入失败 和回滚
  • 原文地址:https://www.cnblogs.com/lixiang1013/p/7103270.html
Copyright © 2020-2023  润新知