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中为什么要用self探讨
Apr 14 Python
Python简单计算文件夹大小的方法
Jul 14 Python
详解Python函数作用域的LEGB顺序
May 14 Python
Python画图学习入门教程
Jul 01 Python
Swift 3.0在集合类数据结构上的一些新变化总结
Jul 11 Python
python dataframe NaN处理方式
Dec 26 Python
PyCharm GUI界面开发和exe文件生成的实现
Mar 04 Python
django models里数据表插入数据id自增操作
Jul 15 Python
Python 在局部变量域中执行代码
Aug 07 Python
Python爬虫之Selenium警告框(弹窗)处理
Dec 04 Python
python中的class_static的@classmethod的巧妙用法
Jun 22 Python
 python中的元类metaclass详情
May 30 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
新手学PHP之数据库操作详解及乱码解决!
2007/01/02 PHP
PHP 数据结构 算法描述 冒泡排序 bubble sort
2011/07/10 PHP
解析php file_exists无效的解决办法
2013/06/26 PHP
PHP+ajaxfileupload+jcrop插件完美实现头像上传剪裁
2014/06/09 PHP
简要剖析PHP的Yii框架的组件化机制的基本知识
2016/03/17 PHP
CodeIgniter常用知识点小结
2016/05/26 PHP
thinkphp5 框架结合plupload实现图片批量上传功能示例
2020/04/04 PHP
Javascript加载速度慢的解决方案
2014/03/11 Javascript
Angular中$compile源码分析
2016/01/28 Javascript
jQuery操作动态生成的内容的方法
2016/05/28 Javascript
详解Vue2.0 事件派发与接收
2017/09/05 Javascript
nodejs前端模板引擎swig入门详解
2018/05/15 NodeJs
vue组件横向树实现代码
2018/08/02 Javascript
详解Eslint 配置及规则说明
2018/09/10 Javascript
vue构建动态表单的方法示例
2018/09/22 Javascript
使用post方法实现json往返传输数据的方法
2019/03/30 Javascript
[57:18]DOTA2上海特级锦标赛主赛事日 - 1 败者组第一轮#3VP VS VG
2016/03/03 DOTA
Python的ORM框架SQLAlchemy入门教程
2014/04/28 Python
简单的抓取淘宝图片的Python爬虫
2014/12/25 Python
深入解析Python设计模式编程中建造者模式的使用
2016/03/02 Python
简单学习Python time模块
2016/04/29 Python
python测试mysql写入性能完整实例
2018/01/18 Python
python 识别图片中的文字信息方法
2018/05/10 Python
Python 循环终止语句的三种方法小结
2019/06/24 Python
Python操作注册表详细步骤介绍
2020/02/05 Python
Python的历史与优缺点整理
2020/05/26 Python
屈臣氏官方旗舰店:亚洲享负盛名的保健及美妆零售商
2019/03/15 全球购物
给客户的道歉信
2014/01/13 职场文书
大学竞选班长演讲稿
2014/04/24 职场文书
2014年医院科室工作总结
2014/12/20 职场文书
综合素质自我评价评语
2015/03/06 职场文书
干部培训工作总结2015
2015/05/25 职场文书
单位收入证明范本
2015/06/18 职场文书
婚宴父母致辞
2015/07/27 职场文书
优秀乡村医生事迹材料(2016精选版)
2016/02/29 职场文书
php 获取音视频时长,PHP 利用getid3 获取音频文件时长等数据
2021/04/01 PHP