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("\033[32;1m[producer]\033[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('\033[31;1mAAAAAAAAAA...\033[0m',time.ctime())
    gevent.sleep(2) #遇到阻塞,IO操作,自动切换
    print('\033[31;1mBBBBBBBBBB...\033[0m',time.ctime())

def func2():
    print('\033[32;1mCCCCCCCCCCC...\033[0m',time.ctime())
    gevent.sleep(1)
    print('\033[32;1mDDDDDDDDDDDDDDDDD...\033[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()

 

posted @ 2017-11-25 12:13  ninxin18  阅读(402)  评论(0编辑  收藏  举报