Python 之协程

之前我们学习了线程、进程的概念,了解了在操作系统中进程是资源分配的最小单位,线程是CPU调度的最小单位。按道理来说我们已经算是把cpu的利用率提高很多了。但是我们知道无论是创建多进程还是创建多线程来解决问题,都要消耗一定的时间来创建进程、创建线程、以及管理他们之间的切换。

随着我们对于效率的追求不断提高,基于单线程来实现并发又成为一个新的课题,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发。这样就可以节省创建线进程所消耗的时间。

为此我们需要先回顾下并发的本质:切换+保存状态

cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制),一种情况是该任务发生了阻塞,另外一种情况是该任务计算的时间过长

ps:在介绍进程理论时,提及进程的三种执行状态,而线程才是执行单位,所以也可以将上图理解为线程的三种状态

一:其中第二种情况并不能提升效率,只是为了让cpu能够雨露均沾,实现看起来所有任务都被“同时”执行的效果,如果多个任务都是纯计算的,这种切换反而会降低效率。

为此我们可以基于yield来验证。yield本身就是一种在单线程下可以保存任务运行状态的方法,我们来简单复习一下:

# 1 yiled可以保存状态,yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级
# 2 send可以把一个函数的结果传给另外一个函数,以此实现单线程内程序之间的切换
# 串行执行
import time
def consumer(res):'''任务1:接收数据,处理数据'''passdef producer():'''任务2:生产数据'''res=[]for i in range(10000000):res.append(i)return resstart=time.time()
res=producer()    # 串行执行
consumer(res)     # 写成consumer(producer())会降低执行效率
stop=time.time()
print(stop-start) # 1.5536692142486572# 基于yield并发执行
import time
def consumer():'''任务1:接收数据,处理数据'''while True:x=yielddef producer():'''任务2:生产数据'''g=consumer()next(g)for i in range(10000000):g.send(i)start=time.time()
# 基于yield保存状态,实现两个任务直接来回切换,即并发的效果
# PS:如果每个任务中都加上打印,那么明显地看到两个任务的打印是你一次我一次,即并发执行的.
producer()
stop=time.time()
print(stop-start) #2.0272178649902344

二:第一种情况的切换。在任务一遇到io情况下,切到任务二去执行,这样就可以利用任务一阻塞的时间完成任务二的计算,效率的提升就在于此。

import time
def consumer():'''任务1:接收数据,处理数据'''while True:x=yielddef producer():'''任务2:生产数据'''g=consumer()next(g)for i in range(10000000):g.send(i)time.sleep(2)start=time.time()
producer()   # 并发执行,但是任务producer遇到io就会阻塞住,并不会切到该线程内的其他任务去执行stop=time.time()
print(stop-start)

  对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程。

协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。为了实现它,我们需要找寻一种可以同时满足以下条件的解决方案:

  • 可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。
  • 作为1的补充:可以检测io操作,在遇到io操作的情况下才发生切换

协程介绍

协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。

需要强调的是:

  • python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
  • 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)

对比操作系统控制线程的切换,用户在单线程内控制协程的切换

优点如下:

  • 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
  • 单线程内就可以实现并发的效果,最大限度地利用cpu

缺点如下:

  • 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
  • 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

总结协程特点:

  • 必须在只有一个单线程里实现并发
  • 修改共享数据不需加锁
  • 用户程序里自己保存多个控制流的上下文栈
  • 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

Greenlet模块

安装 :pip3 install greenlet

from greenlet import greenletdef eat(name):print('%s eat 1' %name)g2.switch('egon')print('%s eat 2' %name)g2.switch()
def play(name):print('%s play 1' %name)g1.switch()print('%s play 2' %name)g1=greenlet(eat)
g2=greenlet(play)g1.switch('egon')#可以在第一次switch时传入参数,以后都不需要

单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度

# 顺序执行
import time
def f1():res=1for i in range(100000000):res+=idef f2():res=1for i in range(100000000):res*=istart=time.time()
f1()
f2()
stop=time.time()
print('run time is %s' %(stop-start)) #10.985628366470337# 切换
from greenlet import greenlet
import time
def f1():res=1for i in range(100000000):res+=ig2.switch()def f2():res=1for i in range(100000000):res*=ig1.switch()start=time.time()
g1=greenlet(f1)
g2=greenlet(f2)
g1.switch()
stop=time.time()
print('run time is %s' %(stop-start)) # 52.763017892837524

greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。

单线程里的这20个任务的代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。。。。如此,才能提高效率,这就用到了Gevent模块。

Gevent模块

安装:pip3 install gevent

Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

# 用法介绍
g1=gevent.spawn(func,1,,2,3,x=4,y=5)  # 创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的
g2=gevent.spawn(func2)
g1.join() # 等待g1结束
g2.join() # 等待g2结束
# 或者上述两步合作一步:gevent.joinall([g1,g2])
g1.value  # 拿到func1的返回值
import gevent
def eat(name):print('%s eat 1' %name)gevent.sleep(2)  # 模拟IO阻塞print('%s eat 2' %name)def play(name):print('%s play 1' %name)gevent.sleep(1)  # 模拟IO阻塞print('%s play 2' %name)g1=gevent.spawn(eat,'egon')
g2=gevent.spawn(play,name='egon')
g1.join()
g2.join()
#或者gevent.joinall([g1,g2])
print('主')

上例gevent.sleep(2)模拟的是gevent可以识别的io阻塞,而time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了

from gevent import monkey;monkey.patch_all() # 必须放到被打补丁者的前面,如time,socket模块之前或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头import gevent
import time
def eat():print('eat food 1')time.sleep(2)print('eat food 2')def play():print('play 1')time.sleep(1)print('play 2')g1=gevent.spawn(eat)
g2=gevent.spawn(play)
gevent.joinall([g1,g2])
print('主')

我们可以用threading.current_thread().getName()来查看每个g1和g2,查看的结果为DummyThread-n,即假线程

from gevent import monkey;monkey.patch_all()
import threading
import gevent
import time
def eat():print(threading.current_thread().getName())print('eat food 1')time.sleep(2)print('eat food 2')def play():print(threading.current_thread().getName())print('play 1')time.sleep(1)print('play 2')g1=gevent.spawn(eat)
g2=gevent.spawn(play)
gevent.joinall([g1,g2])
print('主')# 查看threading.current_thread().getName()

Gevent之同步与异步

from gevent import spawn,joinall,monkey;monkey.patch_all()import time
def task(pid):"""Some non-deterministic task"""time.sleep(0.5)print('Task %s done' % pid)def synchronous():  # 同步for i in range(10):task(i)def asynchronous(): # 异步g_l=[spawn(task,i) for i in range(10)]joinall(g_l)print('DONE')if __name__ == '__main__':print('Synchronous:')synchronous()print('Asynchronous:')asynchronous()
#  上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。
#  初始化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall 函数,
#  后者阻塞当前流程,并执行所有给定的greenlet任务。执行流程只会在 所有greenlet执行完后才会继续向下走。

Gevent之应用举例一

from gevent import monkey;monkey.patch_all()
import gevent
import requests
import timedef get_page(url):print('GET: %s' %url)response=requests.get(url)if response.status_code == 200:print('%d bytes received from %s' %(len(response.text),url))start_time=time.time()
gevent.joinall([gevent.spawn(get_page,'https://www.python.org/'),gevent.spawn(get_page,'https://www.yahoo.com/'),gevent.spawn(get_page,'https://github.com/'),
])
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/454399.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ip变换器苹果系统_加密锁能为高铁做什么?铁路电力变换器系统告诉您

代表国之重器的高铁,是怎样保护他们在软件上投资的专有技术不被仿冒、逆向工程和篡改呢?与一般的软件保护加密不同,高铁运行中的实时控制器对物理环境有着严苛的要求,如铁路电力变换器系统,须满足典型工业应用的扩展温…

华为服务器ibmc怎么装系统,华为服务器安装操作系统

《华为服务器安装操作系统》由会员分享,可在线阅读,更多相关《华为服务器安装操作系统(11页珍藏版)》请在人人文库网上搜索。1、华为服务器安装操作系统 1把网线接到服务器管理口上,2288服务器管理口在服务器背后中下 部位置,有Mg…

英语面试(自我介绍,电话面试..)

大凡找工作的人,都有对面试的担心,而英语面试最令人头痛。面试气氛总是紧张的,一紧张就容易出错,中文“台词”都会结巴,何况英语! 可俗话说,养兵千日,用兵一时,学了那么多…

服务端渲染与 Universal React App

随着 Webpack 等前端构建工具的普及,客户端渲染因为其构建方便,部署简单等方面的优势,逐渐成为了现代网站的主流渲染模式。而在刚刚发布的 React v16.0 中,改进后更为优秀的服务端渲染性能作为六大更新点之一,被 React…

zxing .net 多种条码格式的生成

下载地址:http://zxingnet.codeplex.com/ zxing.net是.net平台下编解条形码和二维码的工具,使用非常方便。 本文主要说明一下多种类型条码的生成。 适用的场景,标签可视化设计时,自定义条码类型,预览。 遍历zxing支持的…

k8s dashboard_windows10 部署 docker+k8s 集群

下面是经过踩坑之后的 windows10 单机部署 docker k8s 步骤,其中有几处比较关键的地方需要注意,后面加粗标注,下面就开始吧!0、下载cmder在 windows 上有一个趁手的命令行工具非常有必要,推荐 Cmder,下面是…

Python 之网络编程基础

套接字(socket)初使用 基于TCP协议的socket tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端 server端 import socket sk socket.socket() sk.bind((127.0.0.1,8898)) # 把地址绑定到套接字 sk.listen() …

ajax on ture,细数Ajax请求中的async:false和async:true的差异

实例如下:function test(){var temp"00";$.ajax({async: false,type : "GET",url : userL_checkPhone.do,complete: function(msg){alert(complete);},success : function(data) {alert(success);tempdata;temp"aa";}});alert(temp);…

阿里云邮箱登录日志中有异地IP登录是怎么回事?该怎么办?

注意,请先到阿里云官网 领取幸运券,除了价格上有很多优惠外,还可以参与抽奖。详见:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode2a7uv47d&utm_source2a7uv47d以下可能:1、您的邮…

面试之网络编程和并发

1、简述 OSI 七层协议。 物理层:主要基于电器特性发送高低电压(1、0),设备有集线器、中继器、双绞线等,单位:bit 数据链路层:定义了电信号的分组方式,设备:交换机、网卡、网桥,单位&…

redis 远程主机强迫关闭了一个现有的连接_记一次Redis+Getshell经验分享

你是我患得患失的梦,我是你可有可无的人,毕竟这穿越山河的箭,刺的都是用情之疾的人。前言:当我们接到一个授权渗透测试的时候,常规漏洞如注入、文件上传等尝试无果后,扫描端口可能会发现意外收获。知己知彼…

无线连接 服务器,服务器无线远程连接

服务器无线远程连接 内容精选换一换华为云帮助中心,为用户提供产品简介、价格说明、购买指南、用户指南、API参考、最佳实践、常见问题、视频帮助等技术文档,帮助您快速上手使用华为云服务。使用Mac版Microsoft Remote Desktop工具,远程连接W…

面试前您该做的事情

选自本人作品:《软件性能测试与LR实战》 无论您是刚刚毕业的大学生朋友,还是已经有工作经验的同行,大家都不可避免的面临一个问题就是找工作或者换工作的问题。在整个应聘过程中,面试无疑是最具有决定性意义的重要环节&#xff0c…

IO模型

IO模型介绍 传统的网络IO模型包括五种: blocking IO 阻塞IOnonblocking IO 非阻塞IOIO multiplexing IO多路复用signal driven IO 信号驱动IOasynchronous IO 异步IO 由于signal driven IO(信号驱动IO)在实际中…

重温数据结构:树 及 Java 实现(转)

转自:http://blog.csdn.net/u011240877/article/details/53193877 读完本文你将了解到: 什么是树树的相关术语 根节点父亲节点孩子节点叶子节点如上所述节点的度树的度节点的层次树的高度树的深度树的两种实现 数组表示链表表示的节点树的几种常见分类及…

Powershell检测AD账户密码过期时间并邮件通知

脚本主要实现了两个功能 : 一能判断账户密码的过期时间并通过邮件通知到账户; 二是将这些即将过期的账户信息累计通知到管理员。 脚本如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051…

js list删除指定元素_vue.js

vue.js 中M V MV代表哪一部分 <插值表达式&#xff08;v-cloak v-text v-html v-bind&#xff08;缩写是:&#xff09; v-on&#xff08;缩写是&#xff09; v-model v-for v-if v-show &#xff09;<body><div id"app"><!-- 使用 v-cloak 能够解决…

修改db2管理服务器,创建DB2管理服务器的两种情况

DB2管理服务器在创建时分为创建一个和创建多个两种情况&#xff0c;下面就为您详细介绍这两种创建DB2管理服务器的情况&#xff0c;供您参考学习。一、创建DB2管理服务器(只能创建一个)1、首先创建管理服务组用户(可不建)命令&#xff1a;sudo groupadd dasadm12、创建用户命令…

系统程序员成长计划-走近专业程序员

转载时请注明出处和作者联系方式 文章出处&#xff1a;http://www.limodev.cn/blog 作者联系方式&#xff1a;李先静 <xianjimli at hotmail dot com> 需求简述 用C语言编写一个双向链表。如果你有一定的C语言编程经验&#xff0c;这自然是小菜一碟。有的读者可能连一个…

Python 内置模块之 asyncio(异步iO)

python3.0&#xff0c;标准库里的异步网络模块&#xff1a;select(非常底层) &#xff0c;第三方异步网络库&#xff1a;Tornado&#xff0c;gevent python3.4&#xff0c;asyncio&#xff1a;支持 TCP &#xff0c;子进程 现在的asyncio&#xff0c;有了很多的模块已经在支持…