Python协程操作之gevent(yield阻塞,greenlet),协程实现多任务(有规律的交替协作执行)用法详解


Posted in Python onOctober 14, 2019

本文实例讲述了Python 协程操作之gevent(yield阻塞,greenlet),协程实现多任务(有规律的交替协作执行)用法。分享给大家供大家参考,具体如下:

实现多任务:进程消耗的资源最大,线程消耗的资源次之,协程消耗的资源最少(单线程)。

gevent实现协程,gevent是通过阻塞代码(例如网络延迟等)来自动切换要执行的任务,所以在进行IO密集型程序时(例如爬虫),使用gevent可以提高效率(有效利用网络延迟的时间去执行其他任务)。

GIL(全局解释器锁)是C语言版本的Python解释器中专有的,GIL的存在让多线程的效率变低(哪个线程抢到锁,就执行哪个线程)。在IO密集型程序中,多线程依然比单线程效率高(GIL通过IO阻塞自动切换多线程)。

解决GIL(全局解释器锁)的问题的三种方法:1、不要用C语言版本的Python解释器。2、让子线程运行其他语言代码(例如:主线程运行Python代码,子线程运行C语言代码(C语言的动态库))。3、多进程代替多线程(多进程可以利用多核CPU)。

demo.py(协程底层原理,yield):

import time
# 带yield的函数并不是函数,而是一个生成器模板,返回一个生成器(可用于遍历迭代)
def task_1():
  while True:
    time.sleep(0.1)
    yield # 阻塞,等待next迭代解阻塞
def task_2():
  while True:
    time.sleep(0.1)
    yield
def main():
  t1 = task_1() # 返回的t1是一个生成器。 此时并未执行task_1中的代码
  t2 = task_2()
  # 先让t1运行一会,当t1中遇到yield的时候,再返回到24行,然后
  # 执行t2,当它遇到yield的时候,再次切换到t1中
  # 这样t1/t2/t1/t2的交替运行,最终实现了多任务....协程
  while True:
    next(t1) # 执行task_1中的代码,遇到yield阻塞task_1。 等待下一次next激活。
    next(t2) # 实现task_1与task_2交替执行,实现多任务...协程
if __name__ == "__main__":
  main()

demo.py(greenlet实现协程,封装了yield):

from greenlet import greenlet # 需要安装greenlet模块 sudo pip3 install greenlet (python2.x使用pip) 
import time
def test1():
  while True:
    print("---A--")
    gr2.switch()  # 切换到gr2中的任务。
    time.sleep(0.5)
def test2():
  while True:
    print("---B--")
    gr1.switch()  # 切换到gr1中的任务。
    time.sleep(0.5)
gr1 = greenlet(test1) # greenlet 底层封装了yield。
gr2 = greenlet(test2)
#切换到gr1中运行
gr1.switch()

demo.py(gevent实现协程,封装了greenlet,遇到阻塞代码自动切换协程任务):

import gevent # 需要安装gevent模块 sudo pip3 install gevent (python2.x使用pip) 
import time
def f1(n):
  for i in range(n):
    print(gevent.getcurrent(), i)
    gevent.sleep(0.5)  # 为了提高协程效率,遇到阻塞类代码,会自动切换协程任务。
    # time.sleep(0.5)  # 阻塞类代码必须使用gevent自己包装的代码,原生阻塞类代码不会切换协程任务。 
              # 可以使用monkey.patch_all()将所有原生阻塞类代码替换成gevent包装的阻塞类代码。 
def f2(n):
  for i in range(n):
    print(gevent.getcurrent(), i) # <Greenlet "Greenlet-0" at 0x7f4a09b34648: f1(5)> 0
    gevent.sleep(0.5)
    # time.sleep(0.5)
def f3(n):
  for i in range(n):
    print(gevent.getcurrent(), i)
    gevent.sleep(0.5)
    # time.sleep(0.5)
g1 = gevent.spawn(f1, 5) # gevent其实是对greenlet的封装。
g2 = gevent.spawn(f2, 5) # 第一个参数f2表示协程执行的具体任务(函数),第二个参数5表示要传给f2的参数
g3 = gevent.spawn(f3, 5)
g1.join()  # 遇到阻塞类代码,自动切换协程任务。
g2.join()
g3.join()

运行结果:

<Greenlet at 0x1985030: f1(5)> 0
<Greenlet at 0x1b431c8: f2(5)> 0
<Greenlet at 0x1b43140: f3(5)> 0
<Greenlet at 0x1985030: f1(5)> 1
<Greenlet at 0x1b431c8: f2(5)> 1
<Greenlet at 0x1b43140: f3(5)> 1
<Greenlet at 0x1985030: f1(5)> 2
<Greenlet at 0x1b431c8: f2(5)> 2
<Greenlet at 0x1b43140: f3(5)> 2
<Greenlet at 0x1985030: f1(5)> 3
<Greenlet at 0x1b431c8: f2(5)> 3
<Greenlet at 0x1b43140: f3(5)> 3
<Greenlet at 0x1985030: f1(5)> 4
<Greenlet at 0x1b431c8: f2(5)> 4
<Greenlet at 0x1b43140: f3(5)> 4

demo.py(gevent打补丁,monkey自动替换原生阻塞类代码。重要,常用):

import gevent # 需要安装gevent模块 sudo pip3 install gevent (python2.x使用pip)
import time
from gevent import monkey
# gevent打补丁
monkey.patch_all() # 将所有原生阻塞类代码自动替换成gevent包装的阻塞类代码。 
def f1(n):
  for i in range(n):
    print(gevent.getcurrent(), i)
    time.sleep(0.5) # 会自动替换成 gevent.sleep(0.5)
def f2(n):
  for i in range(n):
    print(gevent.getcurrent(), i)
    time.sleep(0.5)
def f3(n):
  for i in range(n):
    print(gevent.getcurrent(), i)
    time.sleep(0.5)
# g1 = gevent.spawn(f1, 5)
# g2 = gevent.spawn(f2, 5)
# g3 = gevent.spawn(f3, 5)
# g1.join()
# g2.join()
# g3.join()
# 一种简便写法
gevent.joinall([
    gevent.spawn(f1, 5),
    gevent.spawn(f2, 5),
    gevent.spawn(f3, 5)
])

运行结果:

<Greenlet at 0x22e0938: f1(5)> 0
<Greenlet at 0x22e09c0: f2(5)> 0
<Greenlet at 0x22e0a48: f3(5)> 0
<Greenlet at 0x22e0938: f1(5)> 1
<Greenlet at 0x22e09c0: f2(5)> 1
<Greenlet at 0x22e0a48: f3(5)> 1
<Greenlet at 0x22e0938: f1(5)> 2
<Greenlet at 0x22e09c0: f2(5)> 2
<Greenlet at 0x22e0a48: f3(5)> 2
<Greenlet at 0x22e0938: f1(5)> 3
<Greenlet at 0x22e09c0: f2(5)> 3
<Greenlet at 0x22e0a48: f3(5)> 3
<Greenlet at 0x22e0938: f1(5)> 4
<Greenlet at 0x22e09c0: f2(5)> 4
<Greenlet at 0x22e0a48: f3(5)> 4

demo.py(gevent底层原理):

import socket
import time
tcp_server_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_server_tcp.bind(("", 7899))
tcp_server_tcp.listen(128)
tcp_server_tcp.setblocking(False) # 设置套接字为非堵塞的方式。 (接收数据时如果没有接到数据(阻塞)那么就抛异常,否则正常接收数据。)
client_socket_list = list() # 用于保存与客户端连接的套接字。
while True:
  # time.sleep(0.5)
  try:
    new_socket, new_addr = tcp_server_tcp.accept() # 用抛异常的方式代替阻塞。
  except Exception as ret:
    print("---没有新的客户端到来---")
  else:
    print("---只要没有产生异常,那么也就意味着 来了一个新的客户端----")
    new_socket.setblocking(False) # 设置套接字为非堵塞的方式。 (如果需要阻塞就直接抛异常代替阻塞)
    client_socket_list.append(new_socket)
  for client_socket in client_socket_list:
    try:
      recv_data = client_socket.recv(1024)  # 用抛异常的方式代替阻塞。
    except Exception as ret:
      print("----这个客户端还没有发送过来数据----")
    else:
      if recv_data:
        # 对方发送过来数据
        print("----客户端发送过来了数据-----")
      else:
        # 对方调用close 导致了 recv返回
        client_socket.close()
        client_socket_list.remove(client_socket)
        print("---客户端已经关闭----")

希望本文所述对大家Python程序设计有所帮助。

Python 相关文章推荐
在pandas中一次性删除dataframe的多个列方法
Apr 10 Python
Python获取昨天、今天、明天开始、结束时间戳的方法
Jun 01 Python
python+os根据文件名自动生成文本
Mar 21 Python
python制作英语翻译小工具代码实例
Sep 09 Python
Python 可变类型和不可变类型及引用过程解析
Sep 27 Python
Python如何优雅获取本机IP方法
Nov 10 Python
PyTorch加载预训练模型实例(pretrained)
Jan 17 Python
Python实现自动访问网页的例子
Feb 21 Python
python爬虫基础知识点整理
Jun 02 Python
python利用 keyboard 库记录键盘事件
Oct 16 Python
运行Python编写的程序方法实例
Oct 21 Python
利用Python判断整数是否是回文数的3种方法总结
Jul 07 Python
Python 闭包,函数分隔作用域,nonlocal声明非局部变量操作示例
Oct 14 #Python
win10子系统python开发环境准备及kenlm和nltk的使用教程
Oct 14 #Python
python web框架Flask实现图形验证码及验证码的动态刷新实例
Oct 14 #Python
执行Django数据迁移时报 1091错误及解决方法
Oct 14 #Python
解析Python3中的Import
Oct 13 #Python
Python英文文章词频统计(14份剑桥真题词频统计)
Oct 13 #Python
Python 转换RGB颜色值的示例代码
Oct 13 #Python
You might like
ThinkPHP基本的增删查改操作实例教程
2014/08/22 PHP
详解PHP数组赋值方法
2015/11/07 PHP
php文件包含目录配置open_basedir的使用与性能详解
2017/04/03 PHP
php给数组赋值的实例方法
2019/09/26 PHP
JQuery AJAX实现目录浏览与编辑的代码
2008/10/21 Javascript
JS获取鼠标坐标的实例方法
2013/07/18 Javascript
JavaScript语言核心数据类型和变量使用介绍
2013/08/23 Javascript
jQuery中slice()方法用法实例
2015/01/07 Javascript
JavaScript实现向setTimeout执行代码传递参数的方法
2015/04/16 Javascript
浅谈JavaScript 的执行顺序
2015/08/07 Javascript
jQuery实现的超酷苹果风格图标滑出菜单效果代码
2015/09/16 Javascript
一个用jquery写的判断div滚动条到底部的方法【推荐】
2016/04/29 Javascript
jQuery使用经验小技巧(推荐)
2016/05/31 Javascript
js学习阶段总结(必看篇)
2016/06/16 Javascript
简单的JS控制button颜色随点击更改的实现方法
2017/04/17 Javascript
Vue 第三方字体图标引入 Font Awesome的方法
2018/09/28 Javascript
python使用rsa加密算法模块模拟新浪微博登录
2014/01/22 Python
儿童学习python的一些小技巧
2018/05/27 Python
python pexpect ssh 远程登录服务器的方法
2019/02/14 Python
python3在同一行内输入n个数并用列表保存的例子
2019/07/20 Python
对于Python深浅拷贝的理解
2019/07/29 Python
使用Python完成15位18位身份证的互转功能
2019/11/06 Python
解决python 读取 log日志的编码问题
2019/12/24 Python
python 实现简单的FTP程序
2019/12/27 Python
详解Open Folder as PyCharm Project怎么添加的方法
2020/12/29 Python
解决HTML5中滚动到底部的事件问题
2019/08/22 HTML / CSS
Clarks鞋法国官方网站:英国其乐鞋品牌
2018/02/11 全球购物
美国体育用品商店:Academy Sports + Outdoors
2020/01/04 全球购物
留学推荐信怎么写
2014/01/25 职场文书
医学类个人求职信范文
2014/02/05 职场文书
餐饮部总监岗位职责范文
2014/02/13 职场文书
上课打牌的检讨书
2014/02/15 职场文书
房产买卖委托公证书
2014/04/04 职场文书
交通违章检讨书
2014/09/21 职场文书
测量员岗位职责
2015/02/14 职场文书
导游词之无锡古运河
2019/11/14 职场文书