为啥 asyncio 单线程 速度还能那么快

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为啥 asyncio 单线程 速度还能那么快相关的知识,希望对你有一定的参考价值。

我对于asyncio的了解是 通过await去处理阻塞的corountines,直接处理下一个消息,但因为不是多线程,感觉还是在一个一个处理啊,为什么会比multithread速度快呢?
比如:

Number of Process Multiprocessing Asyncio
2 25.5s 7.5s
4 15.4s 7.0s
8 11.5s 7.2s
回答问题 的 注意下素质好么 不用无聊灌水浪费大家时间

莫凡python asyncio教程那边过来的吧?

我这两天也在学习asyncio,之前都是用threading写爬虫,刚拿我之前的一个贴吧爬虫的需求尝试了一下,确实是快,爬某吧会员列表,协程的速度比我线程的速度要快好几倍:

协程与多线程速度对比1

(测试1,爬1000张页面。aio.py脚本是协程;asy2.py脚本是多线程,线程数是15)

协程与多线程速度对比2

(测试2,爬10000张页面。aio.py脚本是协程;asy2.py脚本是多线程,线程数是100)

至于为什么快,我的理解是,协程更适合高IO操作,低计算密集的程序,而爬虫本质上其实都是IO操作(请求网络内容并下载是网络IO,下载下来并写入硬盘也是本地IO)。

而多线程之所以在这方面没有协程快,主要是因为:

线程开销更大(主要在于占用cpu资源,以及多线程之间上下文切换的开销,参考:多线程的线程开销)

而实际上爬虫在干什么呢?不停下载数据而已,基本都不需要cpu计算,所以要cpu的开销做什么呢?


最后再来看一下跑2个脚本时我的电脑资源占用对比:

跑协程时

跑多线程(线程数100)时

所以为什么协程快?这就一目了然了。

(P.S. 用asyncio需要确保写对了才行,不然可能会反而比多线程慢,要注意。这方面我也还在学习中。)

参考技术A

这个也要看他的主频是多高了.比如单核CPU3.06就比双核2.6的主频要高,但是单线程是运行一个程序或少个程序能显示出来他的速度,运行东西多了的话就会被多线程给比下去了.

追问

那请问asyncio是单线程么 我还是不太理解

参考技术B 介绍.
MMO游戏毫无疑问是当前主流趋势,不管从技术角度还是从流行趋势.曾几何时,写一个MMO游戏是需要大量的预算和非常复杂的底层的编程技术.最近,事情发生变化了.很多基于动态语言的现在框架可以做到处理数以千计的玩家连接.(数以千计,咋一听起来有点挫啊,可是仔细想想一个单独的进程也差不多就是这个级别,大型的MMO都是在这个基础上做多进程扩展).同时html 5和WebSockets标准能够实现网页运行的实时画面游戏.
python可能不是创建可扩展的非阻塞服务器的最流行的工具,尤其和node.js对比.但是最新版本的python瞄准了这一问题.asyncio和async/await能够是异步的语言看起来和常规的代码一样直接.所以我们来通过这些新特性来演示一下如何创建一个MMO游戏.
变成异步
一个gameserver能够处理大量玩家的并行连接,同时要做到实时.一个方法是创建线程,可是这种方法并不适用.运行几千个线程,cpu就要不停的切换线程(context switching),这样就非常抵消.如果用多进程呢?那更糟糕,因为需要占用更多的内存.使用pyuthon还有一个更大的问题 - 通常python解释器(CPython)设计上不能做到真正的多线程,它的目标是单线程的效率. 这就是它使用GIL(global interpreter lock)的原因,这就使得python不同同时运行代码,来防止共享对象的使用. 一般情况下,解释器切换到另一个线程发生在当前线程正咋等待io或者其他.这确实能够做到费阻塞的io.因为只阻塞在一个线程里.然而,这并不能利用多线程的优势,因为不能够同时运行代码,即使是在多核cpu上面.其实呢, 在单线程里面也是完全可以做到非组赛io的,这样就避免了大量的context-switching.
这种单线程非阻塞的实现通过纯python就可以做到.你需要的是select.你自己要写一个事件循环.这种方法需要你把逻辑都写在一个地方, 然而你的应用会很快变成非常复杂的状态机.有很多框架来简化这个方法,最流行的是tornado和twisted.他们都是通过回调实现了非常复杂的协议.(有点像node.js)这些框架运行自己的事件循环,在特定事件发生时调用你定义的回调.虽然这样已经非常好了,但是这个风格是callback,代码会变得脆弱(不至于吧).和这个相对应的是同步代码.为什么不在一个线程里做到这些呢?
那么我就就要讨论一个新概念, microthreads,(是叫微线程么).意思就是在单线程里面同步运行很多任务.当你调用一个阻塞的任务,背后的"manager"会运行一个事件循环.当一个事件发生,这个manager就会通知等待这个事件的任务.这个任务就继续运行直到遇到一个阻塞,然后有把运行任务交给manager.

作者:default
链接:https://www.jianshu.com/p/8a51f641da07
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
参考技术C 我们首先要明白,上边的种种分析,都是为了营造一个Redis很快的氛围!官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)。 参考技术D 首先需要明确一点,asyncio使用单线程、单个进程的方式切换(通常程序等待读或写数据时就是切换上下文的时机),那这样效率高嘛?实践是检验真理的唯一标准。我们用之前介绍的concurrent.futures和asyncio分别试验下。当然下面例子的结果仅供参考,因为无法保证被请求的网站的服务水平,这会造成对结果或多或少有影响,可以多跑几次综合的来看。仅供参考!

多线程也不一定比单线程快

多线程似乎一直给我们这样的印象就是多线程比单线程快,其实这是一个伪命题.事无绝对,多线程有时候确实比单线程快,但也有很多时候没有单线程那么快. 首先简单区分一下并发性(concurrency)和并行性(parallel).并行是说同一时刻有多条命令在多个处理器上同时执行.并发是说同一时刻只有一条指令执行,只不过进程(线程)指令在CPU中快速轮换,速度极快,给人看起来就是”同时运行”的印象,实际上同一时刻只有一条指令进行. 但实际上如果我们在一个应用程序中使用了多线程,线程之间的轮换以及上下文切换是需要花费很多时间的,这样的话当我们执行类似循环之类的操作的时候,是不是就意味着单线程一定会比多线程快呢(因为单线程的执行没有线程切换的时间消耗),看下面一段代码(出自<<Java并发编程的艺术>>)

public class ConcurrencyTest 
    private static final long count = 1000000000;

    public static void main(String[] args) 
        try 
            concurrency();
         catch (InterruptedException e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        
        serial();
    

    private static void concurrency() throws InterruptedException 
        long start = System.currentTimeMillis();
        Thread thread = new Thread(new Runnable() 

            @Override
            public void run() 
                 int a = 0;
                 for (long i = 0; i < count; i++)
                 
                     a += 5;
                 
            
        );
        thread.start();
        int b = 0;
        for (long i = 0; i < count; i++)
        
            b--;
        
        thread.join();
        long time = System.currentTimeMillis() - start;
        System.out.println("concurrency : " + time + "ms,b=" + b);
    

    private static void serial() 
        long start = System.currentTimeMillis();
        int a = 0;
        for (long i = 0; i < count; i++)
        
            a += 5;
        
        int b = 0;
        for (long i = 0; i < count; i++)
        
            b--;
        
        long time = System.currentTimeMillis() - start;
        System.out.println("concurrency : " + time + "ms,b=" + b);
    


这段代码的运行结果是当count值比较小的时候单线程的运行速度比多线程要快,当count的值比较大的时候多线程的速度大概会是单线程的两倍为什么会这样呢,理论上来说执行上述这种代码操作单线程不需要上下文切换应该肯定比多线程要快才对呀.
 其实是因为我们异想天开了,CPU的资源又不是全部都给你这个Java程序使用的,别的程序也要占用CPU资源啊.比如说当我们这个程序还没有运行的时候其实可能操作系统中已经有50个线程在运行了,那么当我们这个程序运行的时候,单线程就会占用CPU51分之1的时间(假设每个线程占用时间相等),多线程就会占用CPU52分之2的时间,你说哪个会运行的更快呢.当然当数量比较少的时候上下文之间的轮换会占用相对较多的时间所以这个时候虽然多线程占用CPU52分之2的时间,但是切换也需要很多的时间所以就比单线程要慢
.

以上是关于为啥 asyncio 单线程 速度还能那么快的主要内容,如果未能解决你的问题,请参考以下文章

为啥在不同线程中调用 asyncio subprocess.communicate 会挂起?

单线程多任务异步抓取(asyncio)

为啥线程中的 python asyncio 进程在 Linux 上似乎不稳定?

为啥使用了线程池速度没有变化呢python

redis——redis的一些核心把握

python爬虫 asyncio aiohttp aiofiles 单线程多任务异步协程爬取图片