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 相关文章推荐
Python中使用Boolean操作符做真值测试实例
Jan 30 Python
Python元组拆包和具名元组解析实例详解
Mar 26 Python
Python八大常见排序算法定义、实现及时间消耗效率分析
Apr 27 Python
Python matplotlib 画图窗口显示到gui或者控制台的实例
May 24 Python
python对日志进行处理的实例代码
Oct 06 Python
Python3实现爬虫爬取赶集网列表功能【基于request和BeautifulSoup模块】
Dec 05 Python
pyqt5 禁止窗口最大化和禁止窗口拉伸的方法
Jun 18 Python
Python实现最常见加密方式详解
Jul 13 Python
在python中用print()输出多个格式化参数的方法
Jul 16 Python
opencv导入头文件时报错#include的解决方法
Jul 31 Python
Python自动发送和收取邮件的方法
Aug 12 Python
Scrapy实现模拟登录的示例代码
Feb 21 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
Terran魔法科技
2020/03/14 星际争霸
PHP 面向对象 final类与final方法
2010/05/05 PHP
php设置编码格式的方法
2013/03/05 PHP
JXTree对象,读取外部xml文件数据,生成树的函数
2007/04/02 Javascript
jQuery层次选择器选择元素使用介绍
2013/04/18 Javascript
网页中返回顶部代码(多种方法)另附注释说明
2013/04/24 Javascript
阻止子元素继承父元素事件具体思路及实现
2013/05/02 Javascript
jQuery中[attribute]选择器用法实例
2014/12/31 Javascript
JavaScript中数据结构与算法(二):队列
2015/06/19 Javascript
AngularJS使用指令增强标准表单元素功能
2016/07/01 Javascript
微信小程序 wx:for的使用实例详解
2017/04/27 Javascript
详解JavaScript调用栈、尾递归和手动优化
2017/06/03 Javascript
Vue中render方法的使用详解
2018/01/26 Javascript
vue路由拦截及页面跳转的设置方法
2018/05/24 Javascript
vue-music 使用better-scroll遇到轮播图不能自动轮播问题
2018/12/03 Javascript
使用uni-app开发微信小程序的实现
2019/12/13 Javascript
JS写滑稽笑脸运动效果
2020/05/28 Javascript
使用Python抓取模板之家的CSS模板
2015/03/16 Python
Python中计算三角函数之cos()方法的使用简介
2015/05/15 Python
Python实现PS滤镜功能之波浪特效示例
2018/01/26 Python
python numpy数组的索引和切片的操作方法
2018/10/20 Python
连接pandas以及数组转pandas的方法
2019/06/28 Python
python学生管理系统的实现
2020/04/05 Python
python安装mysql的依赖包mysql-python操作
2021/01/01 Python
西班牙拥有最佳品牌的动物商店:Animalear.com
2018/01/05 全球购物
在购买印度民族服饰:Soch
2020/09/15 全球购物
个人工作主要事迹
2014/05/08 职场文书
数学教研活动总结
2014/07/02 职场文书
教师党的群众路线教育实践活动学习心得体会
2014/10/30 职场文书
意向协议书
2015/01/27 职场文书
泰山导游词
2015/02/02 职场文书
2015年护理工作总结范文
2015/04/03 职场文书
2016年“12.4”法制宣传日活动总结
2016/04/01 职场文书
导游词之山东孔庙
2019/11/04 职场文书
关于vue-router-link选择样式设置
2022/04/30 Vue.js
html中两种获取标签内的值的方法
2022/06/16 jQuery