Python异步编程并发比较之循环、进程、线程、协程

如果你不相信努力和时光,那么成果就会是第一个选择辜负你的。不要去否定你自己的过去,也不要用你的过去牵扯你现在的努力和对未来的展望。不是因为拥有希望你才去努力,而是去努力了,你才有可能看到希望的光芒。Python异步编程并发比较之循环、进程、线程、协程,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

image

服务端

现在有一个api接口 http://127.0.0.1:18081/hello 批量请求该接口,该接口中有一个5s的阻塞。使用循环,多进程,多线程,协程等四种方式,一共请求10次,比较总的请求耗时。

import time
from flask import Flask

app = Flask(__name__)


@app.route('/hello')
def hello_world():
    time.sleep(5)
    return "hello world"


if __name__ == '__main__':
    app.run(port=8090, host="0.0.0.0")

四种请求方法

请求函数

请求接口使用最常见好用的http请求包requests,三种请求方法使用同一个函数。
函数如下:

def blocking_way():
    res = requests.get("http://172.16.9.124:8090/hello")
    return res.content

循环

循环调用请求函数10次

# 同步
def sync_way():
    res = []
    for i in range(10):
        res.append(blocking_way())
    return len(res)

start = time.time()
res = sync_way()
print(res)
end = time.time()
print("**********sync************")
print(end-start)

结果:
50.0388023853302

多进程

开启10个进程并发请求函数

# 多进程
def process_way():
    workers = 10
    with futures.ProcessPoolExecutor(workers) as executor:
        futs = {executor.submit(blocking_way) for i in range(10)}

    return len([fut.result() for fut in futs])

start = time.time()
res = process_way()
end = time.time()
print("**************process***********")
print(end-start)

结果:
5.066945791244507

多线程

开启10个线程并发请求函数

# 多线程
def thread_way():
    worker = 10
    with futures.ThreadPoolExecutor(worker) as executor:
        futs = {executor.submit(blocking_way) for i in range(10)}

    return len([fut.result() for fut in futs])

start = time.time()
res = thread_way()
end = time.time()
print("**************threading***********")
print(end-start)

结果:
5.034665822982788

协程

开启10个协程

import aiohttp
import asyncio


async def fetch(url):
    async with aiohttp.ClientSession(loop=loop) as session:
        async with session.get(url) as response:
            response = await response.read()
            return response


if __name__ == "__main__":
    import time
    start = time.time()
    url = "http://127.0.0.1:8090/hello"
    loop = asyncio.get_event_loop()
    tasks = [fetch(url) for i in range(10)]
    res = loop.run_until_complete(asyncio.gather(*tasks))
    end = time.time()
    print(end-start)

结果:
5.018295049667358

耗时比较

并发类型 耗时 单位秒
循环 50.0388023853302
多进程 5.066945791244507
多线程 5.034665822982788
协程 5.018295049667358

分析

同步
每一次请求会阻塞5s,因为10个请求是按照顺序执行,所有一共阻塞50s左右

多进程
开启10个进程,每一个进程完成一次请求,请求之间是互相隔离的,10个请求不存在阻塞。理论上来说10个请求相当于1个请求,所以也就相当于1次请求的时间5s左右

多线程
多线程是一个进程中的并发,也就是说10次请求是在一个进程中完成的。由于GIL锁的存在,一个Python进程中,只允许有一个线程处于运行状态。

为什么线程结果还是如预期,耗时缩减到了十分之一?
因为python线程的调度机制。python遇到阻塞时当前线程会释放GIL,让别的线程有执行机会。所以一个线程执行到 requests.get 时让出GIL,下一个线程执行,这个过程就不存在阻塞。
当第一个让出GIL锁的线程下一次被调度到就有可能已经完成接口请求,下面就是执行剩下的逻辑。整个执行过程主要是阻塞的时间,业务逻辑耗时非常少,所以从10个请求整体来看是非阻塞的。

为什么进程的时间略多于线程呢?
因为进程切换时的上下文切换花费时间高于线程。
进程在上下文切换是需要保存当前进程的寄存器,内存状态,所以耗时比较长。而线程切换耗时较少,所以多线程略快于多进程。

协程
从结果来看,协程似乎是最快的。虽然这里数据量较少,但是从理论分析可以得知这样的结论:协程是用户态的并发,没有cpu调度,协作式的cpu机制比线程的cpu竞争机制要快,因为协程中cpu一直在用户态,没有发生切换,对比线程少了10次切换。

结论

由此可以看出在IO频繁的业务中适合用多线程、协程

对比

类型 特点 优点 缺点
同步 同步阻塞的网络交互方式,效率低十分低下
多进程 使用多个cpu核心执行任务 有效减少同步过程的时间阻塞 进程切换开销较大,由于内存资源的限制,一个任务开启的进程数有限
多线程 使用一个cpu核心开启多个线程执行 执行任务更加轻量级,支持数百到数千的数量规模。遇到阻塞任务自动让出GIL,可以有效解决阻塞 GIL让多核cpu同时只能有一个工作。调度策略是抢占式,需要业务控制
协程 一个线程下的并发,没有cpu切换 没有cpu调度,使用系统的事件通知,耗时最少 协程并发需要相应模块的支持,目前模块异步的支持较少

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/196501.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!