Python初探并发编程

Python初探并发编程


前言

在用Python进行日常开发中,难免不会遇到一些重复性的,高耗时的任务,如果只是简单的串行运行开发的程序,那么就无法使用系统的全部性能,并且会浪费很多时间,并发编程就能够很好的解决这一类问题,下面一系列的教程是我在学习Python并发编程时的笔记,写成教程的方式方便大家学习。

1.了解基本概念

想要使用并发编程,首先需要了解一些基本的概念:

假设有任务一、二、三。任务一耗时15小时,投入5小时,等待10小时,任务二耗时10小时,投入2小时,等待8小时,任务三耗时10小时,投入10小时。 ps:这里标题的并发编程是大概念,即包括多线程、多进程

1.串行
串行示意图
一次只能执行一个任务,前一个任务完成了才能执行下一个任务(一个人[核心]干活)
2.并行
并行示意图
即Python多进程:多个任务同时进行,在同一个时间段可以进行多个任务(三个人[核心]干活)
3.并发

并发示意图
即Python多线程:在任务一完成投入任务进入等待时间时候任务二开始执行,任务二进入等待时间时任务三开始执行
(一个人[核心]干活)

在Python中,虽然 多线程协程 在严格意义上来说是串行(即上图的并发),但是执行效率要比一般的串行程序高得多。一般的串行程序在程序阻塞的时候,只能干等着,不能去做其他的事情,就如用看在线视频缓存时间,一般视频缓存的时候只能干等着,这样的效率是十分的低的。在学会并发编程后,在程序的阻塞期间,其他的任务开始执行,就如在视频缓存的时候聊会天,看下评论,先干其他的事情,提高效率。

2.单线程VS多线程VS多进程

文字看多了会感到很无聊很空洞,还不如几行代码来的实在,下面的代码样例需要有以下的Python基础:1. 函数装饰器 2. 多线程基本使用 3.多进程基本使用 不懂暂时也问题不大,这篇本来就是 介绍文章,看完之后能对这些术语有大概的认识,对结论有些印象就可以了。

在进行对比之前,首先定义常用的四种类型的场景

  • CPU计算密集型
  • 磁盘IO密集型
  • 网络IO密集型
  • 模拟IO密集型
# Cpu计算密集型,150w次运算
def cpu(count=500000,x=1,y=1):  
    for i in range(count):
        x = x + x
        y = y + y


# 磁盘读写密集型,50w次写入
def disk(count=500000):
    with open('temp.txt','w+') as file:
        for x in range(count):
            file.write('Writing...\t')


# 网络io密集型
header = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
}
url = 'https://github.com/ckfanzhe'

def request():
    try:
        page = requests.get(url,header)
        html = page.content  # 获取内容
        return html
    except Exception as e:
        return f'error!{e}'

# 模拟io密集型
def simulation(count=2):
    time.sleep(count)

对比的指标,我们用时间来考量,进行同一任务,用时最少,效率越高

为了使代码看起来更加整洁,这里还需要定义一个简单的 计时器装饰器,它是用来记录每个函数的运行时间

# 装饰器,用来得到函数运行时间
def timer(name):
    def wrapper(func):
        def deco(*args,**kw):
            typ_e = kw.setdefault('type',None)
            to = time.time()
            func(*args,**kw)
            tw = time.time()
            cost = tw -to  # 函数运行时间

            print(f"{name}-{typ_e} 用时:{cost} 秒")
        return deco
    return wrapper

下面是单线程、多线程、多进程的运行函数定义

# 单线程
@timer('单线程')
def single(func, type=''):
    for i in range(10):
        func()

# 多线程
@timer('多线程')
def multi_thread(func, type=''):
    thread_list = []
    for i in range(10):
        th = Thread(target=func, args=())
        thread_list.append(th)
        th.start()
    e = len(thread_list)

    while True:
        for thr in thread_list:
            if not thr.is_alive():
                e -= 1
        if e <= 0:
            break

# 多进程
@timer('多进程')
def multi_process(func, type=''):
    process_list = []
    for i in range(10):
        pr = Process(target=func, args=())
        process_list.append(pr)
        pr.start()
    e = process_list.__len__()
    while True:
        for pro in process_list:
            if not pro.is_alive():
                e -= 1
        if e <= 0:
            break

运行函数的结果:

if __name__ == '__main__':
    print(f'本机cpu核心数为{os.cpu_count()}')
    print('----单线程----\n')
    single(cpu, type="CPU计算密集型")
    single(disk, type="磁盘IO密集型")
    single(request, type="网络IO密集型")
    single(simulation, type="模拟IO密集型")
    print('\n----多线程----\n')
    multi_thread(cpu, type="CPU计算密集型")
    multi_thread(disk, type="磁盘IO密集型")
    multi_thread(request, type="网络IO密集型")
    multi_thread(simulation, type="模拟IO密集型")
    print('\n----多进程----\n')
    multi_process(cpu, type="CPU计算密集型")
    multi_process(disk, type="磁盘IO密集型")
    multi_process(request, type="网络IO密集型")
    multi_process(simulation, type="模拟IO密集型")
'''
本机cpu核心数为8
----单线程----
单线程-CPU计算密集型 用时:71.7639057636261 秒
单线程-磁盘IO密集型 用时:2.519289493560791 秒
单线程-网络IO密集型 用时:12.698022365570068 秒
单线程-模拟IO密集型 用时:20.00567054748535 秒

----多线程----
多线程-CPU计算密集型 用时:111.34905052185059 秒
多线程-磁盘IO密集型 用时:10.482915878295898 秒
多线程-网络IO密集型 用时:1.866039752960205 秒
多线程-模拟IO密集型 用时:2.007629632949829 秒

----多进程----
多进程-CPU计算密集型 用时:16.204625368118286 秒
多进程-磁盘IO密集型 用时:2.43847393989563 秒
多进程-网络IO密集型 用时:2.813472032546997 秒
多进程-模拟IO密集型 用时:3.2443151473999023 秒
'''

汇总:

种类CPU计算密集型磁盘IO密集型网络IO密集型模拟IO密集型
单线程71.762.5212.7020.00
多线程111.3510.481.872.00
多进程16.202.442.813.24

分析:

  • CPU密集型任务:多线程对比单线程,不仅没有优势,而且因为Python的GIL全局锁,它需要不多的进行加锁、释放操作,然而切换线程非常耗时,就如一个人同时干很多事情,而且事情间又要不断的来回切换,所以效率要低下很多。多进程,由于是多个核心共同进行计算,相当于几个人同时做同一件事情,所以效率要高很多,但是也取决于参加的人数(CPU核心数)

  • IO密集型任务:它可以是读写文件的磁盘IO,进行请求的网络IO,与数据库进行交互的数据库IO,这一类都是计算量特别少,一般都是耗时在IO的等待上,所以在进行IO密集型任务时,多线程比单线程体现出了明显的优势,虽然磁盘密集型IO比单线程要慢,但只是在操作同一个文件再进行比较下的结果,有一定局限性

  • 模拟IO密集型:使用sleep来模拟IO的等待时间,可以明显的体现出多线程的优势,每个任务需要睡眠2s,20个任务就需要睡眠20s,使用单线程的时候,可以明显感受到延时,而使用多线程时,10个sleep是同时运行的,所以最终的时间也就是2s

结论:

  1. 多进程虽然比单线程快很多,但是它需要更高的性能支持
  2. 多线程适合IO密集型场景 例如:爬虫、多文件读写、socket编程
  3. 多进程适合大量运算的场景 例如:机器学习、深度学习、大数据分析
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值