Python中协程用法代码详解


Posted in Python onFebruary 10, 2018

本文研究的主要是python中协程的相关问题,具体介绍如下。

Num01?>协程的定义

协程,又称微线程,纤程。英文名Coroutine。

首先我们得知道协程是啥?协程其实可以认为是比线程更小的执行单元。 为啥说他是一个执行单元,因为他自带CPU上下文。这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程。 只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的。

Num02?>协程和线程的差异

那么这个过程看起来和线程差不多。其实不然, 线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。 操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。

Num03?>协程带来的问题

协程有一个问题,就是系统并不感知,所以操作系统不会帮你做切换。 那么谁来帮你做切换?让需要执行的协程更多的获得CPU时间才是问题的关键。

举个例子如下:

目前的协程框架一般都是设计成 1:N 模式。所谓 1:N 就是一个线程作为一个容器里面放置多个协程。 那么谁来适时的切换这些协程?答案是有协程自己主动让出CPU,也就是每个协程池里面有一个调度器, 这个调度器是被动调度的。意思就是他不会主动调度。而且当一个协程发现自己执行不下去了(比如异步等待网络的数据回来,但是当前还没有数据到), 这个时候就可以由这个协程通知调度器,这个时候执行到调度器的代码,调度器根据事先设计好的调度算法找到当前最需要CPU的协程。 切换这个协程的CPU上下文把CPU的运行权交个这个协程,直到这个协程出现执行不下去需要等等的情况,或者它调用主动让出CPU的API之类,触发下一次调度。

Num04?>协程的好处

在IO密集型的程序中由于IO操作远远慢于CPU的操作,所以往往需要CPU去等IO操作。 同步IO下系统需要切换线程,让操作系统可以在IO过程中执行其他的东西。 这样虽然代码是符合人类的思维习惯但是由于大量的线程切换带来了大量的性能的浪费,尤其是IO密集型的程序。

所以人们发明了异步IO。就是当数据到达的时候触发我的回调。来减少线程切换带来性能损失。 但是这样的坏处也是很大的,主要的坏处就是操作被 “分片” 了,代码写的不是 “一气呵成” 这种。 而是每次来段数据就要判断 数据够不够处理哇,够处理就处理吧,不够处理就在等等吧。这样代码的可读性很低,其实也不符合人类的习惯。

但是协程可以很好解决这个问题。比如 把一个IO操作 写成一个协程。当触发IO操作的时候就自动让出CPU给其他协程。要知道协程的切换很轻的。 协程通过这种对异步IO的封装 既保留了性能也保证了代码的容易编写和可读性。在高IO密集型的程序下很好。但是高CPU密集型的程序下没啥好处。

Num05?>yield实现一个简单协程案例

import time
def A():
  while True:
    print("----我是A函数---")
    yield
    time.sleep(0.5)
def B(c):
  while True:
    print("----我是B函数---")
    next(c)
    time.sleep(0.5)
if __name__ == '__main__':
  a = A()
  B(a)

# 结果如下:
# ----我是B函数---
# ----我是A函数---
# ----我是B函数---
# ----我是A函数---
# ----我是B函数---
# ----我是A函数---
# ----我是B函数---
# ----我是A函数---
# ----我是B函数---
# ----我是A函数---
# ......

Num06?>greenlet版本实现协程案例

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : xiaoke
from greenlet import greenlet
import time


def test1():
  while True:
    print("---我是A函数--")
    gr2.switch()
    time.sleep(0.5)


def test2():
  while True:
    print("---我是B函数--")
    gr1.switch()
    time.sleep(0.5)


def main():
  # 切换到gr1中运行
  gr1.switch()


if __name__ == '__main__':
  gr1 = greenlet(test1)
  gr2 = greenlet(test2)
  main()

# 结果如下:
# ---我是A函数--
# ---我是B函数--
# ---我是A函数--
# ---我是B函数--
# ---我是A函数--
# ---我是B函数--
# ---我是A函数--
# ---我是B函数--
# ......

Num07?>gevent实现协程案例

原理:其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。

由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : xiaoke
import gevent


def task1(n):
  for i in range(n):
    print('----task1-----')

    gevent.sleep(1)
    # time.sleep(1) # time.sleep没有让gevent感知到等待


def task2(n):
  for i in range(n):
    print('----task2-----')

    gevent.sleep(1)
    # time.sleep(1)


def main():
  g1 = gevent.spawn(task1, 5)
  g2 = gevent.spawn(task2, 5)

  g1.join()
  g2.join()


if __name__ == "__main__":
  main()


# 结果如下:
# ----task1-----
# ----task2-----
# ----task1-----
# ----task2-----
# ----task1-----
# ----task2-----
# ----task1-----
# ----task2-----
# ----task1-----
# ----task2-----

gevent并发下载器

实际代码里,我们不会用gevent.sleep()去切换协程,而是在执行到IO操作时,gevent自动切换,代码如下

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : xiaoke

import urllib.request # py3

import gevent
from gevent import monkey
# 猴子补丁,将标准库的涉及IO操作方法替换成gevent
monkey.patch_all() 

# 协程的任务函数
def my_download(url):
  print('GET %s' % url)

  response = urllib.request.urlopen(url)
  data = response.read()

  print('下载 %d bytes from %s' % (len(data), url))


def main():
  g1 = gevent.spawn(my_download, 'http://www.google.cn')
  g2 = gevent.spawn(my_download, 'http://www.qq.com')
  g3 = gevent.spawn(my_download, 'http://www.baidu.com')

  gevent.joinall([g1, g2, g3]) # 等待指定的协程结束


if __name__ == "__main__":
  main()

# 结果如下:
# GET http://www.google.cn
# GET http://www.qq.com
# GET http://www.baidu.com
# 下载 102221 bytes from http://www.baidu.com
# 下载 52297 bytes from http://www.qq.com
# 下载 3213 bytes from http://www.google.cn

#从上能够看到是先获取baidu的相关信息,然后依次是qq
#google,但是收到数据的先后顺序不一定与发送顺序相同,
#这也就体现出了异步,即不确定什么时候会收到数据,顺序不一定.

Num08?>gevent版?TCP服务器

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : xiaoke
import socket
import gevent
from gevent import monkey
#猴子补丁,将标准库的涉及IO操作方法替换成gevent
monkey.patch_all()


# 需要为客户端提供服务
def do_service(connect_socket):
  while True:
    # tcp recv() 只会返回接收到的数据
    recv_data = connect_socket.recv(1024)

    # if recv_data == b'':
    if len(recv_data) == 0:
      # 发送方关闭tcp的连接,recv()不会阻塞,而是直接返回''
      # print('client %s close' % str(client_addr))
      # s.getpeername()  s.getsockname()
      print('client %s close' % str(connect_socket.getpeername()))
      break
    print('recv: %s' % recv_data.decode('gbk'))

def main():

  listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  # 设置允许复用地址,当建立连接之后服务器先关闭,设置地址复用
  listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

  my_addr = ('192.168.105.125', 8080)
  listen_socket.bind(my_addr)

  listen_socket.listen(5) # 设置套接字成监听,5表示一个己连接队列长度
  print('listening...')

  while True:
    # 接受连接请求,创建连接套接字,用于客户端连通信
    connect_socket, client_addr = listen_socket.accept() # accept默认会引起阻塞
    # 新创建连接用的socket, 客户端的地址
    # print(connect_socket)
    print(client_addr)

    # 每当来新的客户端连接,创建协程,由协程和客户端通信
    coroutine_do_service = gevent.spawn(do_service, connect_socket)


if __name__ == "__main__":
  main()

总结

关于协程的问题,面试中好像也会时常被问到,大家一定要注意理解,概念,怎么去实现。

以上就是本文关于Python中协程用法代码详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

Python 相关文章推荐
Python在信息学竞赛中的运用及Python的基本用法(详解)
Aug 15 Python
Python实现二维数组按照某行或列排序的方法【numpy lexsort】
Sep 22 Python
PyQt5每天必学之拖放事件
Aug 27 Python
python递归全排列实现方法
Aug 18 Python
python pygame实现2048游戏
Nov 20 Python
django多个APP的urls设置方法(views重复问题解决)
Jul 19 Python
python监控nginx端口和进程状态
Sep 06 Python
python MultipartEncoder传输zip文件实例
Apr 07 Python
Pycharm编辑器功能之代码折叠效果的实现代码
Oct 15 Python
python实现测试工具(二)——简单的ui测试工具
Oct 19 Python
用 python 进行微信好友信息分析
Nov 28 Python
关于Python中进度条的六个实用技巧分享
Apr 05 Python
Python实现简单生成验证码功能【基于random模块】
Feb 10 #Python
Django中Forms的使用代码解析
Feb 10 #Python
Python中列表与元组的乘法操作示例
Feb 10 #Python
Python程序运行原理图文解析
Feb 10 #Python
Python迭代器和生成器定义与用法示例
Feb 10 #Python
Python中装饰器学习总结
Feb 10 #Python
Python基于hashlib模块的文件MD5一致性加密验证示例
Feb 10 #Python
You might like
ThinkPHP3.1新特性之内容解析输出详解
2014/06/19 PHP
JavaScript创建命名空间的5种写法
2014/06/24 PHP
Thinkphp5行为使用方法汇总
2017/12/21 PHP
jQuery实现的立体文字渐变效果
2010/05/17 Javascript
一个很简单的jquery+xml+ajax的无刷新树结构(无css,后台是c#)
2010/06/02 Javascript
自写的一个jQuery圆角插件
2010/10/26 Javascript
jQuery load方法用法集锦
2011/12/06 Javascript
详解JavaScript中undefined与null的区别
2014/03/29 Javascript
jQuery元素选择器用法实例
2014/12/23 Javascript
artDialog+plupload实现多文件上传
2016/07/19 Javascript
基于BootStrap实现局部刷新分页实例代码
2016/08/08 Javascript
AngularJS自定义服务与fliter的混合使用
2016/11/24 Javascript
js实现弹窗暗层效果
2017/01/16 Javascript
微信小程序 聊天室简单实现
2017/04/19 Javascript
微信小程序日历组件calendar详解及实例
2017/06/08 Javascript
使用vue官方提供的模板vue-cli搭建一个helloWorld案例分析
2018/01/16 Javascript
详解写好JS条件语句的5条守则
2019/02/28 Javascript
js中async函数结合promise的小案例浅析
2019/04/14 Javascript
VUE脚手架具体使用方法
2019/05/20 Javascript
vue中axios的二次封装实例讲解
2019/10/14 Javascript
Node中对非阻塞I/O、事件循环的知识点总结
2020/01/05 Javascript
用PyQt进行Python图形界面的程序的开发的入门指引
2015/04/14 Python
Python三级目录展示的实现方法
2016/09/28 Python
Python数据分析之双色球基于线性回归算法预测下期中奖结果示例
2018/02/08 Python
利用Pandas读取文件路径或文件名称包含中文的csv文件方法
2018/07/04 Python
python 2.7.13 安装配置方法图文教程
2018/09/18 Python
Django框架之中间件MiddleWare的实现
2019/12/30 Python
纪伊国屋新加坡网上书店:Kinokuniya新加坡
2017/12/29 全球购物
捷克钓鱼用品网上商店:Parys.cz
2018/06/15 全球购物
文明班级建设方案
2014/05/15 职场文书
幼儿园大班教育随笔
2015/08/14 职场文书
党员干部学习十八届五中全会精神心得体会
2016/01/05 职场文书
党员反邪教心得体会
2016/01/15 职场文书
Redis Cluster 字段模糊匹配及删除
2021/05/27 Redis
基于PyQt5制作一个群发邮件工具
2022/04/08 Python
详解PyTorch模型保存与加载
2022/04/28 Python