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("\033[32;1m[producer]\033[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("\033[31;1m 李在搞锗\033[0m")
    gevent.sleep(2)#遇到sleep自动切换,模拟IO操作
    print("\033[31;1m 李在又继续搞锗。。。。\033[0m")

def func2():
    print(("\033[32;1m 李切换搞牛。。。\033[0m"))
    gevent.sleep(1)#遇到sleep自动切换
    print(("\033[32;1m 李切换继续搞牛。。。\033[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

 

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

posted on 2017-07-01 19:42  shisanjun  阅读(343)  评论(0编辑  收藏  举报

导航