A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

描述

对于python来说, 多线程是python的软肋。在官方文档表明,根据程序的不同类型,如是I/O密集型,CPU密集型,分别使用多线程,多进程会使性能达到最佳。本文的主要目的是笔者在面试过程中,提及到线程,进程,协程对于爬虫来说,哪个性能会更好(笔者本人认为是进程加协程二者配合会达到更好的效果)。希望通过本文来让其他人对于爬虫的线程,进程,协程使用有更深的理解。



本文涉及的环境模块有:

ubuntu16.  python3.5 cpu 1核  网络正常家庭带宽

multiprocessing , threading, gevent, requests, urllib



试验条件

访问特定url 100次,通过访问的整体耗时,来判别多线程,多进程, 协程的性能对于爬取网络I/O密集型程序的优劣。特定url选定为百度首页https://www.baidu.com



实验前期准备

单线程/单进程 响应时间测速

单线程/单进程 响应时间测速 作为 对照组。在这里选用requests, urllib两个爬虫常用模块进行对比,也希望通过此进行比较这二者间性能差别。



上图为访问100次url,所获得的响应时间。此时间差为time模块获取,因此与实际响应时间偏大。但对于此次分析,不造成任何影响。有几次延迟时间特别高,推测该网站对于爬虫有一定的识别,不代表该模块的性能。



上图是requests,urllib 100此访问时间综合。但是值得考虑的是,笔者采用的是同一url进行100次访问,在网站有爬虫识别的技术,其访问时间会延迟。对于一个爬虫来说,访问者100次同一个网站,其实这很常见。而测试的源代码如下:


url = "http://www.baidu.com"

def LoopRequests():
    count = 0
    t = []
    while True:
        if count > 100:                                    
            break
        s = time.time()
        req = requests.get(url)
        e = time.time()
        time.sleep(0.1)      
        print(e-s)
        red.rpush('x1', e-s)
        count += 1
    return t

import urllib
from  urllib import request

def LoopUrlib():
    count = 0
    t = []
    while True:
        if count > 100:
            break
        s = time.time()
        req = request.Request(url=url)
        rep = request.urlopen(req)
        e = time.time()
        time.sleep(0.1)
        print(">>", e-s)
        #single process thread t.append(e - s)
        red.rpush('x2', e-s)
        count += 1
    return t


协程, 进程,线程对比

            

上图为各自开启了100个协程,100个进程,100个线程进行响应时间对比图,其数值通过三次实验访问的时间均值。其数值为结束100个url请求与开启访问url请求时间之差。值得注意的是开启了100个进程,实际在操作过程中笔者是开启了进程数为10的进程池,在for循环的100次达到的。从上图可以看出,gevent开启100个协程请求访问毫无压力,其总响应时间远低于开启了100个线程。有人会问,“开启线程不应该怎样都会比原先更快吗?怎么比单线程单进程的访问请求更慢?”   其原因是由于python本身的GIL锁导致,详细原因请看文末。



文末结论

我们知道能够开启的线程数,进程数是根据电脑本身cpu核数而定的,因此其数量不能随便增加。在上面开启了100个线程,可能线程数太多造成不必要的线程开销?因此有以下实验,分别开启了10个线程循环十次,5个线程循环二十次:



从上图表明,在减少线程数量时,可以减少线程开启的开销,结果是整体访问时间减少(数据是3次实验均值)。但始终不及gevent开启的协程效率高。其原因是由于python的GIL锁。 笔者参考资料作以下解释,见下一节

python GIL锁

python 的GIL锁并不是python 的特性,而我们使用大多解释器为CPython,其中就有GIL锁的身影。他的存在是保证多线程之间的数据完整性以及状态同步性。在这里推荐读者自己前往python社区进行详细了解JPython, IronPython解释器。



测试程序见如下



url = "http://www.baidu.com"

def LoopRequests():
    count = 0
    t = []
    while True:
        if count > 1:
            break
        s = time.time()
        req = requests.get(url)
        e = time.time()
        # time.sleep(0.1)
        await asyncio.sleep(.1)
        print(e-s)
        # single process thread t.append(e - s)
        es = 1 if e - s > 5 else 0
        red.rpush('x1', es)
        count += 1
    return t

import urllib
from  urllib import request

def LoopUrlib():
    count = 0
    t = []
    while True:
        if count > 1:
            break
        s = time.time()
        req = request.Request(url=url)
        rep = request.urlopen(req)
        e = time.time()
        # time.sleep(0.1)
        await asyncio.sleep(.1)
        print(">>", e-s)
        #single process thread t.append(e - s)
        es = 1 if e - s > 5 else 0
        red.rpush('x2', es)
        count += 1
    return t


import multiprocessing
from multiprocessing import  Pool



def multiPool(mode = ''):
    if mode == 'Loopreuqests':
        st = time.time()
        pool = Pool(10)
        for i in range(100):
            pool.apply_async(LoopRequests)

        pool.close()
        pool.join()
        print('>>>', time.time()-st)
    elif mode == 'LoopUrlib':
        st = time.time()
        pool = Pool(10)
        for i in range(100):
            pool.apply_async(LoopUrlib)

        pool.close()
        pool.join()
        print('>>>', time.time() - st)

def threadTest(mode):
    import threading
    if mode == 'Loopreuqests':
        st = time.time()
        th = []
        for i in range(5):
            th.append(threading.Thread(target=LoopRequests))

        for i in th:
            i.start()
            i.join()
        print('>>>', time.time() - st)
    elif mode == 'LoopUrlib':
        st = time.time()
        th = []
        for i in range(5):
            th.append(threading.Thread(target=LoopUrlib))

        for i in th:
            i.start()
            i.join()
        print('>>>', time.time() - st)


def get(k):
    a = []
    while True:
        try:
            x = red.lpop(k)[1]
            if x == None:raise  ValueError
            a.append(x)
        except Exception as e:
            print(e)
            # break
            return a



import gevent
from gevent import monkey;monkey.patch_all()

def GeventTest():
    s = time.time()
    gt = [gevent.spawn(LoopUrlib) for i in range(1000)]
    gevent.joinall(gt)
    d = time.time()
    # print("gevent:", d-s)
    gt = [gevent.spawn(LoopRequests) for i in range(1000)]
    gevent.joinall(gt)
    print("gevent:", time.time() - d)
---------------------
转载,仅作分享,侵删
作者:hea_gui_lion
原文:https://blog.csdn.net/hea_gui_lion/article/details/85884347

1 个回复

倒序浏览
奈斯,加油
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马