Python之协程

协程操作是单线程进行的,协程通过自拟寄存器记录上下文和栈,实现单线程的高并发

与多线程相比,协程的特点:

  1.必须在只有一个单线程里实现并发
  2.修改共享数据不需加锁
  3.用户程序里自己保存多个控制流的上下文栈
  4.一个协程遇到I/O操作自动切换到其它协程

1.通过yield实现简单的生产者消费者模型,拟协程

# -*- coding:utf-8 -*-
# Author: Wongdu

import time

# 消费者,通过yield把该函数变成一个生成器
def consumer(name):
    print("%s 准备好开始吃蛋糕啦~~~" % name)
    while True:
        # 当yield被赋值时,该函数才继续往下执行代码
        dangao = yield
        print("[%s] 被%s吃啦~~~" % (dangao, name))
        time.sleep(1)

# 生产者
def producer(p_name):
    # 通过生成器的__next__()方法把生成器执行到yield的位置
    c1.__next__()
    c2.__next__()
    n = 0
    while True:
        dangao = '蛋糕%s' % n
        print("33[41;1m[%s] 被[%s]制作出来啦~~33[0m" % (dangao, p_name))
        # 为消费者对象的yield赋值
        c1.send(dangao)
        c2.send(dangao)
        # time.sleep(1)
        n += 1

if __name__ == '__main__':
    c1 = consumer(name='Caiyun')
    c2 = consumer(name='Dudu')

    p = producer(p_name='蛋糕贾师傅')

2.协程之greenlet实现手动I/O切换

# -*- coding:utf-8 -*-
# Author:Wong Du

'''
协程操作是单线程进行的,协程通过自拟寄存器记录上下文和栈,实现单线程的高并发
1.必须在只有一个单线程里实现并发
2.修改共享数据不需加锁
3.用户程序里自己保存多个控制流的上下文栈
4.一个协程遇到IO操作自动切换到其它协程
'''

'''
通过greenlet实现协程间的手动切换
'''
from greenlet import greenlet

def run1():
    print(12)
    # 切换到gr2协程执行
    gr2.switch()
    print(34)
    gr2.switch()

def run2():
    print(56)
    # 切换到gr1协程执行
    gr1.switch()
    print(78)

# 注册协程,记录当前协程状态(类似线程中寄存器位置状态记录)
gr1 = greenlet(run1)
gr2 = greenlet(run2)

# 切换到gr1协程执行
gr1.switch()

3.协程之gevent实现自动I/O切换

# -*- coding:utf-8 -*-
# Author:Wong Du

'''
1.在协程中,通常在遇到I/O读写阻塞时,才进行协程间切换
2.默认情况下,gevent只能识别gevent下的I/O阻塞,如gevent.sleep(),不能识别time.sleep()阻塞
3.通过monkey.patch_all()方法,可以识别python中大部分的I/O阻塞,如time.sleep()
4.下面代码介绍gevent实现自动I/O切换
'''

import gevent
import time

# 通过monkey里的patch_all方法,可以识别python中基本全部I/O阻塞
# from gevent import monkey
# monkey.patch_all()

# 记录程序开始执行时间
start_time = time.time()

def func1():
    print("Running in the func1...")
    # 模拟I/O阻塞,睡2秒
    gevent.sleep(2)
    # time.sleep(2)
    print("33[31;1mRunning in the func1 again...33[0m")

def func2():
    print("Running in the func2...")
    # 模拟I/O阻塞,睡1秒
    gevent.sleep(1)
    # time.sleep(1)
    print("33[32;1mRunning in the func2 again...33[0m")

def func3():
    print("Running in the func3...")
    # 模拟I/O阻塞,睡0.5秒
    gevent.sleep(0.5)
    # time.sleep(0.5)
    print("33[33;1mRunning in the func3 again...33[0m")


# 通过joinall方法以列表的方式注册多个协程
gevent.joinall(
    [
        # 注册和自动运行协程,并记录当前协程状态(类似线程中寄存器位置状态记录)
        gevent.spawn(func1),
        gevent.spawn(func2),
        gevent.spawn(func3),
    ]
)

# 计算程序执行完成花费时间
print("33[41;1mspeed time:%s33[0m" % (time.time()-start_time))
'''
运行结果:
    Running in the func1...
    Running in the func2...
    Running in the func3...
    Running in the func3 again...
    Running in the func2 again...
    Running in the func1 again...
    speed time:2.014172315597534
'''

4.两个协程实现简单异步效果实例

  4.1 爬虫之协程gevent

 1 # -*- coding:utf-8 -*-
 2 # Author:Wong Du
 3 
 4 import time, gevent
 5 from urllib.request import urlopen
 6 
 7 # 为当前程序的所有io操作添加gevent可识别的标记,效果类似gevent.sleep()
 8 from gevent import monkey
 9 monkey.patch_all()
10 
11 # 爬虫函数
12 def func(url):
13     print("Get:", url)
14     # 打开网页url并接收返回结果
15     resq = urlopen(url)
16     # 读取请求返回数据的html页面,以字符串的格式
17     data = resq.read()
18     print("%s size: %s" % (url, len(data)))
19 
20 # 要爬取的网页url列表
21 urls = [
22     'https://www.python.org/',
23     'https://www.imooc.com/',
24     'https://www.cnblogs.com/',
25 ]
26 
27 # 计算通过同步执行爬虫程序花费的时间
28 sync_start_time = time.time()
29 for url in urls:
30     func(url)
31 print("33[31;1msync speed time: %s33[0m" %(time.time()-sync_start_time))
32 # 结果:sync speed time: 3.6082065105438232
33 
34 # 计算通过协程异步执行爬虫程序花费的时间
35 async_start_time = time.time()
36 gevent.joinall(
37     [
38         gevent.spawn(func, 'https://www.python.org/'),
39         gevent.spawn(func, 'https://www.imooc.com/'),
40         gevent.spawn(func, 'https://www.cnblogs.com/'),
41     ]
42 )
43 print("33[32;1masync speed time: %s33[0m" % (time.time()-async_start_time))
44 # 结果:async speed time: 0.5860333442687988
同步与异步

  4.2 通过gevent实现一个简单的异步socket连接处理,实现单线程下的socket连接高并发

 1 # -*- coding:utf-8 -*-
 2 # Author:Wong Du
 3 
 4 import socket
 5 HOST = "localhost"
 6 Port = 8001
 7 client = socket.socket()
 8 client.connect((HOST, Port))
 9 
10 while True:
11     cmd = input("%s#" % HOST)
12     if not cmd:
13         continue
14     client.send(cmd.encode())
15     data = client.recv(1024)
16     print(data.decode())
gevent_socket_client
 1 # -*- coding:utf-8 -*-
 2 # Author:Wong Du
 3 
 4 import socket, gevent
 5 
 6 # 为当前程序的所有io操作添加gevent可识别的标记,效果类似gevent.sleep()
 7 from gevent import monkey
 8 monkey.patch_all()
 9 
10 # 建立连接,并注册协程
11 def server(port):
12     serve = socket.socket()
13     serve.bind(("0.0.0.0", port))
14     serve.listen(3)
15 
16     while True:
17         # 等待连接接入,阻塞,当有连接接入时,往下执行
18         conn, addr = serve.accept()
19 
20         # 注册协程,通过gevent记录并监测该协程的状态(阻塞时切换)
21         gevent.spawn(handle_request, conn)
22 
23 # 连接处理函数
24 def handle_request(conn):
25     print(conn)
26     try:
27         while True:
28             res = conn.recv(1024)
29             print(res.decode())
30             conn.send(res)
31             if not res:
32                 print("client %s is not connected..." % conn.addr)
33                 conn.shutdown(socket.SHUT_WR)
34     except Exception as e:
35         print(e)
36     finally:
37         print("---exec done---")
38         conn.close()
39 
40 if __name__ == "__main__":
41     server(8001)
gevent_socket_server
原文地址:https://www.cnblogs.com/Caiyundo/p/9512877.html