3.4.2 Gevent

Gevent

Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

示例

import gevent
#gevent,自动挡切换

def func1():
    print('Ashley starts running.')    #1
    gevent.sleep(2)    #切换到2
    print('Ashley tumbled over Sarah.')    #6

def func2():
    print('Sarah starts running.')    #2
    gevent.sleep(1)    #切换到3
    print('Sarah tumbled over Nancy.')    #5

def func3():    
    print('Nancy starts running.')    #3
    gevent.sleep(0)    #切换,但无需等待
    print('Nancy tumbled on a hurdle.')    #4

gevent.joinall([
    gevent.spawn(func1),    #spawn引发引起导致造成
    gevent.spawn(func2),
    gevent.spawn(func3)
])

结果

Ashley starts running.
Sarah starts running.
Nancy starts running.
Nancy tumbled on a hurdle.
Sarah tumbled over Nancy.
Ashley tumbled over Sarah.

同步与异步的性能区别

看下面示例

import gevent

def task(pid):
    gevent.sleep(0.5)
    print('Task %s done' % pid)

def synchronous():
    for i in range(1, 10):
        task(i)    #串行,循环10次task函数

def asynchronous():
    threads = [gevent.spawn(task, i) for i in range(10)]
    #发起协程,协程执行task函数,循环10次,总计10条线程,存储在threads里
    gevent.joinall(threads)

print('Synchronous:')
synchronous()    #同步串行
print('Asynchronous:')
asynchronous()    #异步并行

结果

Synchronous:    #这里每条睡0.5秒
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 1 done
Task 2 done
Task 3 done
Task 4 done
Task 5 done
Task 6 done
Task 7 done
Task 8 done

上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。 初始化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall 函数,后者阻塞当前流程,并执行所有给定的greenlet。执行流程只会在 所有greenlet执行完后才会继续向下走。  

遇到IO阻塞时会自动切换任务,下面是一个简单的爬取网页的示例,我们看一下同步与异步的区别:

import gevent, time
from gevent import monkey    #gevent无法识别socket中的I/O操作
from urllib import request
monkey.patch_all()    #有了这个方法,相当于给socket的I/O操作加上了标识

def f(url):
    print('GET: %s' % url)
    resp = request.urlopen(url)
    data = resp.read()    #I/O任务,每当遇到I/O任务会自动切换
    print('%d bytes received from %s.' % (len(data), url))
    # print('Data from %s is: %s.' % (url, data))

urls = ['https://www.python.org/',
        'https://www.yahoo.com/',
        'https://github.com/']

sync_start_time = time.time()
for url in urls:
    f(url)
print('同步Cost: ', time.time() - sync_start_time)

async_start_time = time.time()
gevent.joinall([
    gevent.spawn(f, 'https://www.python.org/'),    #启动协程,执行f函数,并将url传递给f
    gevent.spawn(f, 'https://www.yahoo.com/'),
    gevent.spawn(f, 'https://github.com/'),
])
print('异步Cost: ', time.time() - async_start_time)

结果

GET: https://www.python.org/
48981 bytes received from https://www.python.org/.
GET: https://www.yahoo.com/
334724 bytes received from https://www.yahoo.com/.
GET: https://github.com/
132874 bytes received from https://github.com/.
同步Cost:  25.191901445388794
GET: https://www.python.org/
GET: https://www.yahoo.com/
GET: https://github.com/
48981 bytes received from https://www.python.org/.
132874 bytes received from https://github.com/.
334179 bytes received from https://www.yahoo.com/.
异步Cost:  5.816762208938599

由于每次遇到I/O操作都会自动切换,所以,先输出三条print(‘GET: %s’ % url),当遇到data = resp.read()这步时会自动切换。read()读取属于I/O操作。

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

服务器端

import gevent
from gevent import socket, monkey
monkey.patch_all()

def server(port):    #port=8001
    s = socket.socket()    #实例化一个socket
    host = socket.gethostname()
    s.bind((host, port))
    while True:
        client, addr = s.accept()
        gevent.spawn(handle_request, client)    #客户端每来一条链接启动一个协程

def handle_request(conn):
    try:
        while True:
            data = conn.recv(1024)
            print('Received data: ', 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)    #调用server并传参8001

客户端

import socket

host = socket.gethostname()
port = 8001
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
while True:
    msg = bytes(input('\>: '), encoding='UTF-8')
    s.sendall(msg)
    data = s.recv(1024)
    print('Received: ', repr(data))

为了显示多并发结果,这里我们同时并行三个客户端

服务器端

Received data:  b'\xe8\x80\x8c\xe6\xaf\x8f\xe8\xbf\x87\xe4\xb8\x80\xe5\xa4\xa9\xe6\xaf\x8f\xe4\xb8\x80\xe5\xa4\xa9\xe8\xbf\x99\xe9\x86\x89\xe8\x80\x85'
Received data:  b'\xe4\xbe\xbf\xe7\x88\xb1\xe4\xbd\xa0\xe5\xa4\x9a\xe4\xba\x9b\xe5\x86\x8d\xe5\xa4\x9a\xe4\xba\x9b\xe8\x87\xb3\xe6\xbb\xa1\xe6\xb3\xbb'
Received data:  b'\xe6\x88\x91\xe5\x8f\x91\xe8\xa7\x89\xe6\x88\x91\xe6\x9c\x80\xe7\x88\xb1\xe4\xb8\x8e\xe4\xbd\xa0\xe7\xbc\x96\xe5\x86\x99\xe4\xbb\xa5\xe5\x90\x8e\xe6\x98\x8e\xe5\xa4\xa9\xe7\x9a\x84\xe6\xb7\xb1\xe5\xa4\x9c'

客户端1

\>: 而每过一天每一天这醉者
Received:  b'\xe8\x80\x8c\xe6\xaf\x8f\xe8\xbf\x87\xe4\xb8\x80\xe5\xa4\xa9\xe6\xaf\x8f\xe4\xb8\x80\xe5\xa4\xa9\xe8\xbf\x99\xe9\x86\x89\xe8\x80\x85'
\>: 

客户端2

\>: 便爱你多些再多些至满泻
Received:  b'\xe4\xbe\xbf\xe7\x88\xb1\xe4\xbd\xa0\xe5\xa4\x9a\xe4\xba\x9b\xe5\x86\x8d\xe5\xa4\x9a\xe4\xba\x9b\xe8\x87\xb3\xe6\xbb\xa1\xe6\xb3\xbb'
\>: 

客户端3

\>: 我发觉我最爱与你编写以后明天的深夜
Received:  b'\xe6\x88\x91\xe5\x8f\x91\xe8\xa7\x89\xe6\x88\x91\xe6\x9c\x80\xe7\x88\xb1\xe4\xb8\x8e\xe4\xbd\xa0\xe7\xbc\x96\xe5\x86\x99\xe4\xbb\xa5\xe5\x90\x8e\xe6\x98\x8e\xe5\xa4\xa9\xe7\x9a\x84\xe6\xb7\xb1\xe5\xa4\x9c'
\>: 

以上就实现了一个多并发服务器。

posted @ 2020-01-02 18:17  InfiniteCodes  阅读(169)  评论(0)    收藏  举报