玩转python(7)python多协程,多线程的比较

前段时间在做一个项目,项目本身没什么难度,只是数据存在一个数据接口服务商那儿,这就意味着,前端获取数据需要至少两次http请求,第一次是前端到后端的请求,第二次是后端到数据接口的请求。有时,后端接收到前端的一次请求后,可能需要对多个接口进行请求,按照传统串行执行请求的方法,用户体验肯定是非常糟糕了,而且对计算资源也是极大的浪费,正好前段时间学习了协程和线程的知识,所以我花了一些时间,对几种可行方案进行了测试对比。一开始我使用真正的网络io进行测试,发现这种方法受网络环境影响比较大,为了公平起见,用sleep(0.02)来代替网络io,接下来介绍方案和测试结果。

基本方案:串行执行

import time

def wget(flag):
    time.sleep(0.02) #模拟网络io
    print(flag)

count = 100 #进行100次请求
start = time.time() #开始时刻
for i in range(count):
    wget(i)
end = time.time() #结束时刻
cost = end - start #耗时
print(‘cost:‘ + str(spend))

最终耗时超过2s,毫无疑问,这是最没效率的方案,仅仅是作为参照而已。

改进方案:多线程

多线程的计时方法显然不能照搬串行执行的测试方案,原因在于每个线程启动后,如果调用join()阻塞主线程,那么相当于串行执行,如果不调用join(),那么结束时刻end会在所有线程完成之前就返回,测试结果必然不准。所以我用了一个笨办法:在每个线程结束时打印当前时间戳,把控制台上最后一个时间戳减去线程开始执行的时间戳,就是运行耗时。

import time
import threading

mutex=threading.Lock() #初始化锁对象

def wget():
    time.sleep(0.02) #模拟网络io
    mutex.acquire() #加锁
    print(‘endtime:‘+str(time.time())) #当前线程的结束时刻
    mutex.release() #释放锁

count = 100 #进行100次请求
start = time.time() #开始时刻
print(‘starttime:‘+str(start))
for i in range(count):
    t = threading.Thread(target=wget)
    t.start()

最后结果约为0.08s,这个成绩显然比串行执行好得多,不过程序的运行效率不会随着线程数量的增长而线性增长,原因在于线程创建切换销毁时的开销,极端情况下会造成崩溃。如果线程数不可控制时,这种方案要慎用。

改进方案:线程池

为了解决上面提到的多线程的不足之处,这里使用线程池。

import time
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed, wait, ALL_COMPLETED

mutex=threading.Lock() #初始化锁对象

def wget():
    time.sleep(0.02) #模拟网络io
    mutex.acquire() #加锁
    print(threading.currentThread())
    mutex.release() #释放锁

size = 40 #线程池大小
count = 100 #进行100次请求
start = time.time() #开始时刻
pool = ThreadPoolExecutor(max_workers=size) #线程池对象
tasks = [pool.submit(wget) for i in range(count)]
wait(tasks, return_when=ALL_COMPLETED) #等待所有线程完成
end = time.time() #结束时刻
print(‘spend:‘ + str(end-start))

经过测试后发现,当线程池线程数量设置为40时,耗时最小,约为0.08s,与上一个方案相当,不过随着线程数量继续增加,线程池稳定的特点会显现出来。

改进方案:协程异步

import asyncio
import time

async def wget(flag): #async关键字表明这是一个异步操作
    await asyncio.sleep(0.02) #await相当于yield from
    print(flag)

count = 100 #进行100次请求
start = time.time() #开始时刻
loop = asyncio.get_event_loop() #事件循环对象
tasks = [wget(i) for i in range(count)]
loop.run_until_complete(asyncio.wait(tasks)) #等待所有协程完成
loop.close()
end = time.time() #结束时刻
print(‘spend:‘ + str(end - start))

这个方案的结果让我相当震惊,没有用到任何多线程技术,所有的操作在一个线程上完成,耗时0.06s,是所有方案中耗时最少的。协程更令人振奋的优点在于,协程创建的开销与线程相比完全可以忽略,这意味着,使用多协程可以处理更多的任务。

总结

显然在这几个方案中,协程是最具有优势的,将来如果有时间,我还会对多协程多进程的协同进行测试。

原文地址:https://www.cnblogs.com/bugsheep/p/9226202.html

时间: 2024-08-28 19:31:25

玩转python(7)python多协程,多线程的比较的相关文章

【PYTHON模块】:协程与greenlet、gevent

协程:又称为微线程,英文名称Coroutine. 作用:它拥有自己的寄存器上下文和栈,能保留上一次调用时的状态,可以随时暂停程序,随时切换回来. 优点: ?无需线程上下文切换的开销    ?无需原子操作锁定及同步的开销 ?方便切换控制流,简化编程模型    ?高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题.所以很适合用于高并发处理 缺点:    ?无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上    ?进行阻塞

玩转python(6)协程

多任务系统一般都需要解决一个问题:多个任务如何调度.抢占式调度就是一种很常见的任务调度机制.以单核模式下的进程调度为例,一个进程处于运行状态,其他的处于就绪队列,等到当前运行的进程放弃CPU的使用权,系统将CPU立刻分配给新到达的进程,由于任务的执行顺序是不确定的,看上去就像一堆任务在竞争CPU的使用权,所以这种多任务运行方式叫做"多任务竞争".与之对应的是非抢占式调度.当前任务会持续执行下去直到因为某些原因主动放弃CPU的使用权,各个任务的执行顺序是确定的,就像在互相协作,所以这种多

python并发编程之---协程

1.什么是协程 协程:是单线程下的并发,又称微线程,纤程. 协程是一种用户态的轻量级线程,协程是由用户程序自己控制调度的. 2.需要注意的点: 需要强调的是: #1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行) #2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关) 对比操作系统控制线程的切换,用户在单线程内控制协程的切换 优点

Python并发实践_02_协程

python中实现并发的方式有很多种,通过多进程并发可以真正利用多核资源,而多线程并发则实现了进程内资源的共享,然而Python中由于GIL的存在,多线程是没有办法真正实现多核资源的. 对于计算密集型程序,应该使用多进程并发充分利用多核资源,而在IO密集型程序中,多核优势并不明显,甚至由于大多数时间都是在IO堵塞状态,多进程的切换消耗反而让程序效率更加低下. 而当需要并发处理IO密集型任务时,就需要用到协程(Coroutine).协程并没有系统级的调度,而是用户级的调度方式,避免了系统调用的开销

python学习道路(day11note)(协程,同步与异步的性能区别,url爬网页,select,RabbitMq)

1.协程 1 #协程 又称微线程 是一种用户的轻量级线程 程序级别代码控制 就不用加机器 2 #不同函数 = 不同任务 A函数切到B函数没有进行cpu级别的切换,而是程序级别的切换就是协程 yelied 3 4 #单线程下多个任务流用协程,比如打电话可以切换,nginx 5 #爽妹给你打电话的时候,她不说话,刘征电话过来时候你可以切过去,这时候要是爽妹说话,就会bibi响 6 ''' 7 8 协程的好处: 9 无需线程上下文切换的开销 10 无需原子操作锁定及同步的开销 11 "原子操作(ato

python简单线程和协程学习

python中对线程的支持的确不够,不过据说python有足够完备的异步网络框架模块,希望日后能学习到,这里就简单的对python中的线程做个总结 threading库可用来在单独的线程中执行任意的python可调用对象.尽管此模块对线程相关操作的支持不够,但是我们还是能够用简单的线程来处理I/O操作,以减低程序响应时间. from threading import Thread import time def countdown(n): while n > 0: print('T-minus:

python 高性能编程之协程

用 greenlet 协程处理异步事件 自从 PyCon 2011 协程成为热点话题以来,我一直对此有着浓厚的兴趣.为了异步,我们曾使用多线程编程.然而线程在有着 GIL 的 Python 中带来的性能瓶颈和多线程编程的高出错风险,"协程 + 多进程"的组合渐渐被认为是未来发展的方向.技术容易更新,思维转变却需要一个过渡.我之前在异步事件处理方面已经习惯了回调 + 多线程的思维方式,转换到协程还非常的不适应.这几天我非常艰难地查阅了一些资料并思考,得出了一个可能并不可靠的总结.尽管这个

Python的线程&进程&协程[0] -> 基本概念

基本概念 / Basic Concept 0 简介与动机 / Why Multi-Thread/Multi-Process/Coroutine 在多线程(multithreaded, MT)编程出现之前,计算机程序的执行是由单个步骤序列组成的,该序列在主机的CPU中按照同步顺序执行.即无论任务多少,是否包含子任务,都要按照顺序方式进行. 然而,假定子任务之间相互独立,没有因果关系,若能使这些独立的任务同时运行,则这种并行处理方式可以显著提高整个任务的性能,这便是多线程编程. 而对于Python而

python并发编程之协程

阅读目录 一 引子 二 协程介绍 三 Greenlet 四 Gevent介绍 五 Gevent之同步与异步 六 Gevent之应用举例一 七 Gevent之应用举例二 一 引子 本节的主题是基于单线程来实现并发,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发,为此我们需要先回顾下并发的本质:切换+保存状态 cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制),一种情况是该任务发生了阻塞,另外一种情况是该任务计算的时间过长 ps:在介绍进程理论时,提

Python多任务实现 之协程并发下载多图片

协程是Python中实现多任务一种方式,相比多任务之进程和线程,协程不需要消耗过多的资源,更高效的利用了cpu资源. 在Python中通过gevent封装generator迭代器功能实现多任务的切换.协程在运行过程中是靠程序的耗时操作来实现程序中断.达到切换多任务.至始至终,程序都是在一条主线程里面完成的. 下面是一个利用协程实现多张图片的同时下载. from gevent import monkey import gevent import urllib.request # 设置识别耗时操作