Python 多线程的思考

导语

在知乎等地方经常看到有人问,Python 的多线程是不是鸡肋?为何我用多线程性能一点没有提升,有时候性能反而下降?在这里通过日常工作中遇到的问题以及自己的一些总结,来一探 Python 多线程究竟是不是鸡肋;如果不是,那又该如何使用。

1、遇到的问题

工作中常用到 python 来分析文件,统计数据;随着业务的发展,原先的代码性能受到了一定的挑战,下面根据两个案例来讲解在 python 的使用过程中,遇到的一些问题,以及自己的一些总结。

案例一:数据统计,将文件按照一定的逻辑统计汇总后,入库到本地 db 中。

最开始的代码流程框图:

大概流程:

1、循环读文件,按照一定格式将文本进行拆分计算;

2、根据指定的 key 来统一汇总数据;

3、入库本地 DB,入库时,会先查找 db 中是否存在这条记录,然后再判断是否插入 db 中;相当于这里会有两次 db 操作,一次查询,一次写入。

一开始业务量小,db 数据量少,整个流程耗时较短,在秒级能够完成,随着业务发展,所需时间也有秒级变成了分钟级,十分钟级别等。

测试 1

通过对其中的一个业务某天的数据进行测试,它的耗时主要分部为,读文件(文件大小 1G 左右)耗时 2 秒,逻辑计算及汇总 58s,数据入库 32s;总耗时大概在 1 分半钟。

1.1 方案一

流水线形式的多线程

线程1负责读取数据,然后通过 python 自带的 Qeueue,Q1 传递数据给线程 2; 线程 2 负责逻辑计算,然后通过 Q2 传递数据给线程 3; 线程 3 负责汇总数据,然后通过 Q3 传递数据给线程 4; 线程 4 则入库数据;

这个方案在实现之后立马就被废弃了,它的效率比单进程的效率低很多,通过查看系统调用之后,发现是因为多个线程一直在竞争锁,以及线程切换导致其执行效率还不如单线程。

1.2 方案二

数据分片分段多线程

在不同的时机采用多线程来处理,同时尽量避免多线程对同一资源进行竞争,以减少锁的切换带来的消耗。因此这里在逻辑计算和数据入库阶段分别采用数据分组,多线程执行的方式来进行处理。

分段一、逻辑计算和汇总,将内存读到内存中后,按照线程数量,将数据切分成多块,让第 i 个线程 thread[i]处理第 i 份数据 data[i],最后再将计算得到的 4 份数据汇总,按照相同的 key 进行汇总;得到 sum_data。

分段二、将 sum_data 按照线程数量,切分成多份,同样让 thread[i]处理 sum_data[i]的数据,让他们各自进行数据的查询以及写入操作。

测试 2

对同一个文件进行测试,读文件耗时 2 秒,逻辑计算及汇总 62s,数据入库 10s;总耗时大概在 70 多秒,相比最开始的单线程,时间大概下降了 20 多 s;但可以看出,逻辑计算的时间相比单线程确增加了不少,而入库操作的时间减少了 20 多 s;这里就引发一个问题,逻辑计算跟入库的差异在哪里?为何前面的多线程性能下降,而后面性能确大幅度提高。

这里的原因究竟为何?

案例二

案例 2 的整体流程为,将几份不同的数据源从 db 中取出来,按天取出,经过一定的整合后,汇总插入到一个目标 db 中。

一开始的代码流程:

同样最初的时候,需要整合的数据量较少,db 中的数据量也较少。随着业务增长,每天需要处理的数据量也逐渐增加,并且 db 中的数据量也越来越大,处理的时间也从开始的秒级别也逐渐增加到分钟级别,每次都是统一处理一个月的数据,整体耗时需要几个小时。

测试 3

对某天的数据进行测试,结果为:取数据 整合 耗时 30s;插入数据耗时约 8 分钟。

更改成以下模型: 入库操作同样需要先根据 key 查找当前 db 中是否存在该条数据,不存在则写入。

测试 4

取数据 整合 耗时 30s;插入数据耗时约 2 分钟。

更改之后性能大幅度提升,由原先的 8 分半钟,缩减为不到 2 分半钟左右,缩减的时间主要体现在入库阶段;

从以上两个例子可以看到,当涉及 I/0 操作时,python 的多线程能发挥较好的性能;而当涉及到 CPU 密集型逻辑运算时,python 的多线程性能不升反降。这里都是由于 python 的 GIL 在发挥的作用。

2、了解 python 的 GIL

这里我们使用的解释器为官方实现的 CPython,它是由 C 语言实现的 python 解释器。同时还存在由 Java 实现的 Jython 解释器,由.NET 实现的 IronPython 等解释器。这里我们主要是依据 CPython 来讲解 GIL 锁。

GIL,全称 Global Interpreter Lock, 是一个全局解释器锁,当解释器需要执行 python 代码时,都必须要获得这把锁,然后才能执行。当解释器在遇到到 I/O 操作时会释放这把锁。但如果当程序为 CPU 密集型时,解释器会主动的每间隔 100 次操作就释放这把 GIL 锁,这样别的线程有机会执行,具体的间隔次数是由 sys.setcheckinterval( number ) 来设定这个值,通过 sys.getcheckinterval() 返回这个值,默认为 100。所以,尽管 Python 的线程库直接封装操作系统的原生线程,但 Python 进程,在同一时间只会有一个获得了 GIL 的线程在跑,其它的线程都处于等待状态等着 GIL 的释放。就这样对于 CPU 密集型操作来说,多线程不但不会提升性能,还会因为线程切换,锁竞争等导致性能的下降。

在我们上面的两个例子中,当涉及到数据的查询与插入时,都需要进行 I/O 交互,并且会等待数据库服务器返回,这个时候,线程会主动释放锁,其他线程能够合理利用这个时间,来做同样的事情。

知道了 GIL 之后,我们才能更加合理的使用 python 的多线程,并不是所有场景都适用于多线程。

同样,Python 的多线程也并不是大家所说的鸡肋,在适合的场景用上了,还是能够起到惊艳的作用。

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

许健的专栏

1 篇文章1 人订阅

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏PingCAP的专栏

性能测试工具的 Coordinated Omission 问题

很早之前就看过 Gil 大神的一篇文章《Your Load Generator Is Probably Lying To You - Take The Red ...

2045
来自专栏LanceToBigData

SpringCloud(一)浅谈SpringCloud

现在微服务实在是太火了,所以我们必不可少的是要学习一下SpringCloud了,服务化的核心就是将传统的一站式应用 根据业务拆分成一个一个的服务,而微服务在这...

572
来自专栏about云

构建高并发高可用的电商平台架构实践2

上次发布内容: 如没有接上,可以查看下面原文: 6) 搜索 在电子商务平台中搜索是一个非常的重要功能,主要有接搜索词类目导航、自动提示和搜索排序功能。 开源...

4309
来自专栏京东技术

多级缓存设计详解 | 给数据库减负,刻不容缓!

物流研发部架构师,GIS技术部负责人,2012年加入京东,多年一线团队大促备战经验,负责物流研发一些部门的架构工作,专注于低延迟系统设计与海量数据处理。曾负责青...

1225
来自专栏章鱼的慢慢技术路

游戏服务器开发所需知识点总结

5,对服务器架构有一定了解,比如知道集群怎么搭建,因为这涉及到多台服务器,例如聊天服、战斗服、登录注册服。

673
来自专栏祝威廉

如何提高ElasticSearch 索引速度

这篇文章会讲述上面几个参数的原理,以及一些其他的思路。这些参数大体上是朝着两个方向优化的:

1023
来自专栏贾老师の博客

分布式系统的接口幂等性设计

1633
来自专栏Albert陈凯

2018-10-14 Redisson项目介绍Redisson项目介绍i flym

Rui Gu edited this page <relative-time datetime="2018-05-23T22:07:43Z" title="Ma...

493
来自专栏琯琯博客

大型分布式网站术语分析

一、I/O优化 1、增加缓存,减少磁盘的访问次数。 2、优化磁盘的管理系统,设计最优的磁盘方式策略,以及磁盘的寻址策略,这是在底层操作系统层面考虑的。 3、设...

2164
来自专栏SeanCheney的专栏

深入理解Python异步编程(上)

彻底理解异步编程是什么、为什么、怎么样。深入学习asyncio的基本原理和原型,了解生成器、协程在Python异步编程中是如何发展的。

831

扫码关注云+社区