python异步协程跟多进程多线程哪个效率高?

2020-06-28 17:22发布

1条回答
007
2楼 · 2020-07-16 10:14






目录概念介绍测试环境开始测试测试【单进程单线程】测试【多进程并行】测试【多线程并发】测试【协程+异步】结果对比绘图展示

概念介绍

首先简单介绍几个概念:


进程和线程


进程就是一个程序在一个数据集上的一次动态执行过程(数据集是程序在执行过程中所需要使用的资源)。

线程也叫轻量级进程,它是一个基本的CPU执行单元,是比进程更小的能独立运行的基本单位。

进程和线程的关系:


一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。

资源分配给进程,同一进程的所有线程共享该进程的所有资源。

CPU分给线程,即真正在CPU上运行的是线程。





并行和并发


并行处理是计算机系统中能同时执行两个或更多个处理的一种计算方法。并行处理可同时工作于同一程序的不同方面,其主要目的是节省大型和复杂问题的解决时间。

并发处理指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个CPU上运行,但任一个时刻点上只有一个程序在CPU上运行。

并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以说,并行是并发的子集。多进程是并行的,多线程是并发的。



同步和异步


同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去。

异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。

举个例子,打电话时就是同步通信,发短息时就是异步通信。




测试环境

进行对比测试之前,我们先来创建一个合适的实验环境:

       模拟一个需要等待一定时间才可以获取返回结果的网页。

如果直接用百度、CSDN等站点的话,一方面响应太快、难以看出各种方法的差距,另一方面响应速度会受网速影响、每次发送请求获取响应所需的时间不完全一致导致重复实验结果差距较大,所以在此用Flask模拟一个本地慢速服务器。

flask_server.py代码如下:

fromflaskimportFlask#pipinstallflask

importtime


app=Flask(__name__)


@app.route('/')

defindex():

time.sleep(3)        #休眠3秒再返回结果

return'Hello!'


if__name__=='__main__':

app.run(threaded=True)#以多线程模式启动服务


启动之后,Flask服务默认在127.0.0.1:5000上运行,控制台输出结果如下:

*ServingFlaskapp"flask_server"(lazyloading)

*Environment:production

WARNING:Donotusethedevelopmentserverinaproductionenvironment.

UseaproductionWSGIserverinstead.

*Debugmode:off

*Runningonhttp://127.0.0.1:5000/(PressCTRL+Ctoquit)


在浏览器中访问http://127.0.0.1:5000/等待3秒即会出现Hello!页面,表明简单的慢速服务器搭建完成!


开始测试

首先导入需要的模块,以请求10次为例准备urls,再定义一个get_html_text()函数:

importrequests

importtime

#用于多进程

frommultiprocessingimportProcess

#用于多线程

fromthreadingimportThread

#用于协程+异步

importasyncio

importaiohttp#pipinstallaiohttp


urls=['http://127.0.0.1:5000'for_inrange(10)]


defget_html_text(url):

response=requests.get(url)

returnresponse.text


测试【单进程单线程】

start=time.time()

forurlinurls:

result=get_html_text(url)

print(result)

end=time.time()

print('【单进程单线程】耗时:%s秒'%(end-start))


结果如下:

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

【单进程单线程】耗时:30.15605854988098秒


测试【多进程并行】

start=time.time()

processes=[]

forurlinurls:

p=Process(target=get_html_text,args=(url,))

processes.append(p)

p.start()

forpinprocesses:

p.join()

print('Hello!')

end=time.time()

print('【多进程并行】耗时:%s秒'%(end-start))


结果如下(测试电脑为4核CPU,核心数越大加速越明显):

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

【多进程并行】耗时:5.511234283447266秒


测试【多线程并发】

start=time.time()

threads=[]

forurlinurls:

t=Thread(target=get_html_text,args=(url,))

threads.append(t)

t.start()

fortinthreads:

t.join()

print('Hello!')

end=time.time()

print('【多线程并发】耗时:%s秒'%(end-start))


结果如下:

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

【多线程并发】耗时:3.030653953552246秒


测试【协程+异步】

#因为requests模块不支持异步操作,需要借助aiohttp模块

asyncdefget_html_text_async(url):

asyncwithaiohttp.ClientSession()assession:

asyncwithsession.get(url)asresponse:

text=awaitresponse.text()

returntext


start=time.time()

tasks=[asyncio.ensure_future(get_html_text_async(url))forurlinurls]

loop=asyncio.get_event_loop()

loop.run_until_complete(asyncio.wait(tasks))

fortaskintasks:

print(task.result())

end=time.time()

print('【协程++异步】耗时:%s秒'%(end-start))


结果如下:

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

Hello!

【协程++异步】耗时:3.046288251876831秒


结果对比

len(urls)==1:


len(urls)==4:


len(urls)==10:


len(urls)==100:



单进程单线程是将n次请求顺次执行,每次要等待3秒才能返回结果,故耗时3n+秒;

多进程-并行处理则利用CPU的多核优势,在同一时间并行地执行多个任务,可以大大提高执行效率,但系统实现多进程前需要一些准备工作、将耗费大量时间。

多线程-并发处理和协程+异步的耗时由单进程单线程的3n+秒变成了3+秒!


前者是n个请求几乎同时进行、几乎同时得到响应返回结果。

后者是每当请求任务遇到阻塞(time.sleep(3))时被挂起,n个任务都处于挂起状态后等待3秒,n个请求几乎同时都有了响应,然后挂起的任务被唤醒接着执行,输出请求结果,最后耗时:3秒!(多出来的时间是IO时延)





注意:


搭建的实验环境是慢速服务器,若不进行time.sleep(3)休眠3秒再返回而是立即响应的话,单进程单线程的实际耗时则会大大缩短,请求次数少的话甚至会超过多进程。

而且笔者在Windows4核CPU环境下测试,最多开启4个进程,未能发挥多进程的真实实力。

另外,多进程、多线程还可以通过进程池、线程池来实现,与文中方法耗时基本一致,故未做展示;多进程或多线程与协程异步IO结合的效率尚待测试。



绘图展示






相关问题推荐

  • 回答 3

    换行。比如,print hello\nworld效果就是helloworld\n就是一个换行符。\是转义的意思,'\n'是换行,'\t'是tab,'\\'是,\ 是在编写程序中句子太长百,人为换行后加上\但print出来是一整行。...

  • 回答 42

    十种常见排序算法一般分为以下几种:(1)非线性时间比较类排序:a. 交换类排序(快速排序、冒泡排序)b. 插入类排序(简单插入排序、希尔排序)c. 选择类排序(简单选择排序、堆排序)d. 归并排序(二路归并排序、多路归并排序)(2)线性时间非比较类排序:...

  • 回答 70
    已采纳

    前景很好,中国正在产业升级,工业机器人和人工智能方面都会是强烈的热点,而且正好是在3~5年以后的时间。难度,肯定高,要求你有创新的思维能力,高数中的微积分、数列等等必须得非常好,软件编程(基础的应用最广泛的语言:C/C++)必须得很好,微电子(数字电...

  • 回答 28

    迭代器与生成器的区别:(1)生成器:生成器本质上就是一个函数,它记住了上一次返回时在函数体中的位置。对生成器函数的第二次(或第n次)调用,跳转到函数上一次挂起的位置。而且记录了程序执行的上下文。生成器不仅记住了它的数据状态,生成器还记住了程序...

  • 回答 9

    python中title( )属于python中字符串函数,返回’标题化‘的字符串,就是单词的开头为大写,其余为小写

  • 回答 6

    第一种解释:代码中的cnt是count的简称,一种电脑计算机内部的数学函数的名字,在Excel办公软件中计算参数列表中的数字项的个数;在数据库( sq| server或者access )中可以用来统计符合条件的数据条数。函数COUNT在计数时,将把数值型的数字计算进去;但是...

  • 回答 1

    head是方法,所以需要取小括号,即dataset.head()显示的则是前5行。data[:, :-1]和data[:, -1]。另外,如果想通过位置取数据,请使用iloc,即dataset.iloc[:, :-1]和dataset.iloc[:, -1],前者表示的是取所有行,但不包括最后一列的数据,结果是个DataFrame。...

  • Python入门简单吗2021-09-23 13:21
    回答 45

    挺简单的,其实课程内容没有我们想象的那么难、像我之前同学,完全零基础,培训了半年,直接出来就工作了,人家还在北京大公司上班,一个月15k,实力老厉害了

  • 回答 4

    Python针对众多的类型,提供了众多的内建函数来处理(内建是相对于导入import来说的,后面学习到包package时,将会介绍),这些内建函数功用在于其往往可对多种类型对象进行类似的操作,即多种类型对象的共有的操作;如果某种操作只对特殊的某一类对象可行,Pyt...

  • 回答 8

     相当于 ... 这里不是注释

  • 回答 4

    还有FIXME

  • 回答 3

    python的两个库:xlrd和xlutils。 xlrd打开excel,但是打开的excel并不能直接写入数据,需要用xlutils主要是复制一份出来,实现后续的写入功能。

  • 回答 8

    单行注释:Python中的单行注释一般是以#开头的,#右边的文字都会被当做解释说明的内容,不会被当做执行的程序。为了保证代码的可读性,一般会在#后面加一两个空格然后在编写解释内容。示例:#  单行注释print(hello world)注释可以放在代码上面也可以放在代...

  • 回答 2

    主要是按行读取,然后就是写出判断逻辑来勘测行是否为注视行,空行,编码行其他的:import linecachefile=open('3_2.txt','r')linecount=len(file.readlines())linecache.getline('3_2.txt',linecount)这样做的过程中发现一个问题,...

  • 回答 4

    或许是里面有没被注释的代码

  • 回答 26

    自学的话要看个人情况,可以先在B站找一下视频看一下

没有解决我的问题,去提问