多并发编程基础 之协成
原贴:
https://www.cnblogs.com/gbq-dog/p/10375713.html
今天学习了并发编程中的最后一部分,协程,也是python中区别于java,c等语言中很大不同的一部分
1.协程产生的背景
2.协程的概念
3.yield模拟协程
4.协程中主要的俩个模块
5.协程的应用
开始今日份总结
1.协程产生的背景
之前我们学习了线程、进程的概念,了解了在操作系统中进程是资源分配的最小单位,线程是CPU调度的最小单位。按道理来说我们已经算是把cpu的利用率提高很多了。但是我们知道无论是创建多进程还是创建多线程来解决问题,都要消耗一定的时间来创建进程、创建线程、以及管理他们之间的切换。
随着我们对于效率的追求不断提高,基于单线程来实现并发又成为一个新的课题,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发。这样就可以节省创建线进程所消耗的时间。
为此我们需要先回顾下并发的本质:切换+保存状态
cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制),一种情况是该任务发生了阻塞,另外一种情况是该任务计算的时间过长
ps:在介绍进程理论时,提及进程的三种执行状态,而线程才是执行单位,所以也可以将上图理解为线程的三种状态
一:其中第二种情况并不能提升效率,只是为了让cpu能够雨露均沾,实现看起来所有任务都被“同时”执行的效果,如果多个任务都是纯计算的,这种切换反而会降低效率。
二:第一种情况的切换。在任务一遇到io情况下,切到任务二去执行,这样就可以利用任务一阻塞的时间完成任务二的计算,效率的提升就在于此。
对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程。
协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。为了实现它,我们需要找寻一种可以同时满足以下条件的解决方案:
#1. 可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。 #2. 作为1的补充:可以检测io操作,在遇到io操作的情况下才发生切换
2.协程的概念
协程:在其他语言中很少去用,在python中非常重要的点,对于操作系统来说,线程已经是操作系统能够看到的最小单位,操作系统无法感知协程
- 协程的本质是,就是一条线程分成多份,每一份执行一段代码,多段代码可以在一个线程上来回切换
- 如果能在一段代码执行,在遇到I/O操作的时候,记录此时的状态,去执行另外一段代码,相当于完成利用协程完成了更加充分利用线程的目的
协程利用切换来规避I/O操作带来的好处
- 一条线程可以执行多个任务
- 减少了一个线程的阻塞,帮助线程最大程度的抢占CPU资源
- 协程由于操作系统不可见,不由操作系统控制吗,协程是用户级,减少I/O操作,提高CPU的计算能力
- 协程之间永远数据安全,----因为很多协程本质上就是一条线程
在pthon中,协程是非常重要的。
3.yield模拟协程
那么现在就用yield来模拟协程,毕竟yield也是可以在代码级别记录状态
#代码如下,yield本质是保存现在的状态,send是调用其他函数 def pro():print(1)n = yield 'a'print(n)yield 'b'def com():g = pro()a = next(g)print(a)b = g.send(2)print(b)com()
代码执行顺序如下
相比于串行的去执行,单纯的用yield只会让时间更长
下面用yield测试一下之前用到的生产者消费者模型
#单纯的生产者,消费者模型 import timedef consumer(res):'''单纯的处理数据'''passdef producer():res =[]for i in range(10000000):passreturn resstart = time.time() res = producer() consumer(res) end = time.time() print(end-start) #结果 0.347031831741333#用yield模式尝试import timedef consumer():while True:x = yielddef producer():g = consumer()next(g)for i in range(10000000):g.send(i) start = time.time() #并发的执行任务 producer() end = time.time()print(end-start)#结果 1.8232519626617432
可以看出来,单纯线程之间俩个任务的切换时很可浪费时间的,如果数据量大存储数据也是很需要时间的,每一次切换都需要记住当前的状态,切换回去需要读取之前的状态。
如果我们遇到I/0操作的时候可以自动切换,并且I/O阻塞时间可以和执行代码共享这段时间,才是真正的提高了程序的执行率,yield只是保存了状态。
可以用yield实现一个协程的操作。
4.协程中主要的俩个模块
协程中的主要有俩个模块,俩个模块都是第三方模块,既然是第三方模块那就先说明一下,第三方模块的导入方法
- 方法一:在pycharm中,file—settings—project’xxx’—Project Interpreter—‘+’—搜索要安装的包—InstallPackage
- 方法二:在cmd中 pip install ‘gevent’ 。pip list 查看已经安装的包目录。pip unistalled ‘’ 卸载已经安装的包
这个时候需要俩个第三方模块,一个是gevent,一个是greenlet,不过gevent是greenlet的上层模块,,gevent规避I/O操作,判断程序中的I/O操作,遇到I/O就切换到另一个任务去执行。greenlet主要是俩个任务之间的切换,状态的保存以及读取
4.1 greenlet模块
安装 :pip3 install greenlet
查看代码
import greenletdef eat():print('eat1')g2.switch()print('eat2')g2.switch()def sleep():print('sleep1')g1.switch()print('sleep2')g1 = greenlet.greenlet(eat) g2 = greenlet.greenlet(sleep) g1.switch() #结果 eat1 sleep1 eat2 sleep2
greenlet 模块只是记录了状态并且在切换回去的是读取了状态,并没有真正意思的自动规避I/O操作
4.2 gevent模块
这个时候就需要了gevent模块了
安装:pip3 install gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
#gevent模块的使用方法 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():print('eat1')gevent.sleep(1)print('eat2')def sleep():print('sleep1')gevent.sleep(1)print('sleep2') g1 = gevent.spawn(eat)#实例化一个gevent对象 g2 = gevent.spawn(sleep)#实例化一个gevent对象gevent.joinall([g1,g2])#监测到有I/O就切换
上例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()放到文件的开头。
查看更改后的代码
from gevent import monkey monkey.patch_all()#用来匹配所有的I/O操作 import gevent import time def eat():print('eat1')time.sleep(1)print('eat2')def sleep():print('sleep1')time.sleep(1)print('sleep2') g1 = gevent.spawn(eat)#实例化一个gevent对象 g2 = gevent.spawn(sleep)#实例化一个gevent对象gevent.joinall([g1,g2])#监测到有I/O就切换
最后我们来看一下协程的id号,代码如下
from gevent import monkey monkey.patch_all() import gevent import time from threading import currentThreaddef eat():print('eat:',currentThread())print('eat1')time.sleep(1)print('eat2')def sleep():print('sleep:',currentThread())print('sleep1')time.sleep(1)print('sleep2')g1 = gevent.spawn(eat) g2 = gevent.spawn(sleep)gevent.joinall([g1,g2]) #结果如下 eat: <_DummyThread(DummyThread-1, started daemon 53379528)> eat1 sleep: <_DummyThread(DummyThread-2, started daemon 53380480)> sleep1 eat2 sleep2
我们可以用threading.current_thread().getName()来查看每个g1和g2,查看的结果为DummyThread-n,即假线程
5.协程的应用
对于协程一般使用比较多的地方为网络I/O以及sleep操作,不过一般我们程序代码基本是不会去使用sleep操作,所以日常用的比较多的就是网络爬虫以及socket.server
5.1 网络爬虫简易
看代码
#普通打开方式 import time from urllib import requestdef func(name,url):ret = request.urlopen(url)#获取网页with open(name+'.html','wb') as f:f.write(ret.read()) url_lst = [('python','https://www.python.org/'),('blog','http://www.cnblogs.com/Eva-J/articles/8324673.html'),('pypi','https://pypi.org/project/pip/'),('blog2','https://www.cnblogs.com/z-x-y/p/9237706.html'),('douban','https://www.douban.com/') ] start = time.time() for url_item in url_lst:func(*url_item) end = time.time() print('普通打开方式',end-start)#协程打开方式 from gevent import monkey monkey.patch_all() import gevent from urllib import request import timedef func(name,url):ret = request.urlopen(url)with open(name+'2.html','wb')as f:f.write(ret.read())url_lst = [('python','https://www.python.org/'),('blog','http://www.cnblogs.com/Eva-J/articles/8324673.html'),('pypi','https://pypi.org/project/pip/'),('blog2','https://www.cnblogs.com/z-x-y/p/9237706.html'),('douban','https://www.douban.com/') ] start = time.time() g_list =[] for url_item in url_lst:g = gevent.spawn(func,*url_item)g_list.append(g) gevent.joinall(g_list) end = time.time() print('协程打开方式',end-start)
看结果
普通打开方式 6.35495924949646
协程打开方式 1.931349754333496
我们会发现现在在少量的url状况下是这样,如果在大量的代码下,这个时间就会缩减的更多。
补充:这个是我在测试的时候发现的状况,在已有文件,打开文件并重新写入文件内容,耗费的时间会高很多!
在爬虫的时候还是用协程,这样会更快的拿到我们需要的数据并对其作出分析!
5.2 用协程实现socket.server
看代码
#服务端
#服务端 import socket from gevent import monkey monkey.patch_all() import geventdef talk(conn):while True:msg = conn.recv(1024).decode()conn.send(msg.upper().encode('utf-8'))sk =socket.socket() sk.bind(('127.0.0.1',8500)) sk.listen()while True:conn,addr = sk.accept()gevent.spawn(talk,conn)
#客户端
import socket sk = socket.socket() sk.connect(('127.0.0.1',8500))while True:msg = input('--->').encode('utf-8')sk.send(msg)recv_msg = sk.recv(1024).decode('utf-8')print(recv_msg)sk.close()
转载于:https://www.cnblogs.com/baili-luoyun/p/10391696.html
多并发编程基础 之协成相关推荐
- python中并发编程基础1
并发编程基础概念 1.进程. 什么是进程? 正在运行的程序就是进程.程序只是代码. 什么是多道? 多道技术: 1.空间上的复用(内存).将内存分为几个部分,每个部分放入一个程序,这样同一时间在内存中就 ...
- Java并发编程的艺术-Java并发编程基础
第4章 Java并发编程基础 Java从诞生开始就明智地选择了内置对多线程的支持,这使得Java语言相比同一时期的其他语言具有明显的优势.线程作为操作系统调度的最小单元,多个线程能够同时执行,这将 ...
- 【牛客网】-【并发详解】-【并发编程基础】-【原子类】
目录 并发编程基础 原子类 参考书目: 并发编程基础 在操作系统中,并发是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处 ...
- Java并发编程基础--ThreadLocal
Java并发编程基础之ThreadLocal ThreadLocal是一个线程变量,但本质上是一个以ThreadLocal对象为键.任意对象为值的存储结构,这个结构依附在线程上,线程可以根据一个T ...
- 高并发编程基础(线程池基础)
线程池简单基础介绍: Executor: Executor是Java工具类,执行提交给它的Runnable任务.该接口提供了一种基于任务运行机制的任务提交方法,包括线程使用详细信息,时序等等.Exec ...
- Java并发编程 基础知识学习总结
Java并发编程一直是Java程序员必须懂但又是很难懂的技术内容,这部分的内容我也是反复学习了好几遍才能理解.本篇博客梳理一下最近从<Java 并发编程的艺术>和他人的博客学习Java并发 ...
- Java并发编程的艺术-并发编程基础
Java从诞生开始就明智地选择了内置对多线程的支持,这使得Java语言相比同一时期的其他语言具有明显的优势.线程作为操作系统调度的最小单元,多个线程能够同时执行,这将显著提升程序性能,在多核环境中表现 ...
- PHP 并发编程基础和实践
随着互联网的普及,网民越来越多,这就会造成随便的一个活动,就有可能面临高并发带来的性能问题.而 PHP 是单进程的,很容易造成性能瓶颈,所以 PHP 的并发编程实践显得格外重要.如果想要真正理解并发, ...
- java程序使用异步总线_JAVA并发编程基础
CPU核心 核心(Die)又称为内核,是CPU最重要的组成部分.CPU中心那块隆起的芯片就是核心,是由单晶硅以一定的生产工艺制造出来的,CPU所有的计算.接受/存储命令.处理数据都由核心执行.各种CP ...
最新文章
- C语言素数的乘积,判断一个正整数是否有两个素数的乘积
- 2018人工智能前沿报告:深度学习的应用和价值
- [转]应届毕业生生存法则--工作篇
- 字节跳动内部学习资料泄露!kafka入门教程
- linux系统性能测试之虚拟内存管理篇
- html2canvas改成同步,html2canvas转为图片异步转同步问题(记录)
- oracle关闭数据库容器,Oracle12cr1新特性之容器数据库(CDB)和可插拔数据库(PDB) 的启动和关闭...
- ELK官方文档收集Kibana监视数据编辑
- kettle 插入更新 数据增量_kettle基于时间戳增量更新
- 啊,久违的 win7 开机画面!
- 互联网史话----十亿美金之51
- U3D Distortion
- 【连载】Java笔记——欲品香醇先度根叶
- U盘文件丢失选择什么数据恢复软件好
- 【编程题 】星际密码(详细注释 易懂)
- 敷完面膜后要擦水乳吗_面膜敷完后要擦水乳吗 面膜使用后如何正确护肤
- Python用10行代码爬取大批美女图片
- Python高效办公|批量经纬度转地址
- 计算机应用基础数据处理教案,大学计算机应用基础教案
- 全相位算法c语言表达,基于DSP的全相位FFT频率计设计.pdf
热门文章
- 星巴克Outlook插件允许用户预定会议场地
- Reflex WMS入门系列二十二:物料库存报表
- SSM框架之Mybatis同时传入多个对象及普通参数
- 关于“与google服务器通信时出现问题“
- 第四周——爬虫入门 Day3 7.20
- 4.贪心算法 含例题
- 自如2020新产品发布:全面打造智能租住生活
- 使用Spring Security OAuth2使用JWT生成token及自定义token携带的信息(十)
- 基于单片机的倒车雷达系统设计c语言,基于单片机的倒车雷达设计.doc
- [硬件项目] 2、汽车倒车雷达设计——基于专用倒车雷达芯片GM3101的设计方案与采用CX20106A红外线检测芯片方案对比...