Python示例代码之协程

      协程,又称微线程,纤程。英文名Coroutine。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。协程最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

     Python对协程的支持可以通过generator实现的,在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值,yield是一个关键字,含有该关键字的函数被称为generator,中文意思为生成器。

      yield代码示例

import time

def consumer(name):
    print("%s starting eating ..." %(name))
    while True:
        food = yield
        print("[%s] is eating %s" % (name, food))

def producer():
    for consumer  in consumers:
        consumer.__next__()
    n = 0
    while n < 5:
        n += 1
        for consumer  in consumers:
            consumer.send(n)
        time.sleep(0.1)
        print("[producer] is making %s" % n)

if __name__ == '__main__':
    consumers = []
    for i in range(10):
        consumers.append(consumer("c" + str(i)))
    p = producer()

        遇到IO操作,就进行切换,可以实现执行效率的大幅提升。

        我们可以通过greenlet,实现协程的主动切换。

        greenlet代码示例

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()

      在上例中,启动了两个协程,然后切换到协程1执行,然后根据程序的定义,进行协程间的切换,直至程序执行完毕。

      yield和greenlet都是手动切换,也就是主动切换,gevent可以实现自动切换,即在检测到IO或其他费时操作时会自动切换到其他的子程序。

      gevent代码示例

import gevent

def foo():
    print('Running in foo')
    gevent.sleep(0.3)
    print('context switch to foo again')

def foo2():
    print('Running in foo2')
    gevent.sleep(0.2)
    print('context switch to foo2 again')

def foo3():
    print("Running in foo3")
    gevent.sleep(0.1)
    print("context switch to foo3 again")

gevent.joinall([
    gevent.spawn(foo),
    gevent.spawn(foo2),
    gevent.spawn(foo3),
])

      在上例中,使用gevent启动了三个协程,让其分别执行,在协程内部出现IO等耗时操作时,会自动切换到其他的协程来执行,从而可以极大的提高cpu的利用率。该例子中,我们使用gevent.sleep模拟了IO操作。

      如下使用一个例子使用gevent实现了协程操作,经过测试,使用协程方式从网站读取信息,比同步执行方式,可以节省50%以上的时间。

      注:由于gevent无法直接判断出IO操作,需要使用monkey补丁才能行,所以导入monkey补丁,执行monkey.patch_all()。

from urllib import request
import gevent, time
from gevent import monkey
monkey.patch_all()

def f(url):
    print('GET: %s' % url)
    resp = request.urlopen(url)
    data = resp.read()
    print('%d bytes received from %s.' % (len(data), url))

urls = ['https://www.python.org/',
        'https://www.yahoo.com/',
        'https://github.com/' ]
time_start = time.time()
for url in urls:
    f(url)
print("同步cost",time.time() - time_start)

coroutine_time_start = time.time()
gevent_jobs = []
for url in urls:
    gevent_jobs.append(gevent.spawn(f, url))
gevent.joinall(gevent_jobs)
print("协程cost",time.time() - coroutine_time_start)

      除了greenlet、yield、gevent可以实现协程,select也可以实现协程。

      select的应用场景为IO多路复用,提供了select poll epoll三个方法(其中后两个在Linux中可用,windows仅支持select。

      执行该代码时,会经历如下过程。

        1)上下文切换转换为内核态
        2)将fd(文件描述符)从用户空间复制到内核空间
        3)内核遍历所有fd,查看其对应事件是否发生
        4)如果没发生,将进程阻塞,当设备驱动产生中断或者timeout时间后,将进程唤醒,再次进行遍历
        5)返回遍历后的fd
        6)将fd从内核空间复制到用户空间

      select代码示例

import select
import socket
import queue

server = socket.socket()
server.bind(('localhost', 9000))
server.listen(1000)
server.setblocking(False)

msg_dic = {}
inputs = [server,]
outputs = []
while True:
    readable, writeable,  exceptional= select.select(inputs, outputs, inputs)
    print(readable, writeable, exceptional)
    for r in readable:
        if r is server:
            conn, addr = server.accept()
            print("新连接", addr)
            inputs.append(conn)
            msg_dic[conn] = queue.Queue()
        else:
            data = r.recv(1024)
            print("收到数据", data)
            msg_dic[r].put(data)
            outputs.append(r)
    for w in writeable:
        data_to_client = msg_dic[w].get()
        w.send(data_to_client)
        outputs.remove(w)
    for e in exceptional:
        if e in outputs:
            outputs.remove(e)
        inputs.remove(e)
        del msg_dic[e]

      客户端演示代码如下。

import socket

HOST = 'localhost'
PORT = 9000
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('Received', data)
s.close()
发布了172 篇原创文章 · 获赞 93 · 访问量 38万+

猜你喜欢

转载自blog.csdn.net/chenzhanhai/article/details/94488532
今日推荐