【Python】谈谈Python多线程

2024-08-29 10:32
文章标签 python 多线程 谈谈

本文主要是介绍【Python】谈谈Python多线程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文环境: Python 2.7.10 (CPython)。

文章目录

    • 一、GIL简介
    • 二、Python多线程是否鸡肋
      • 1. 为什么需要多线程呢?
      • 2. 计算密集型 vs. IO密集型
        • 计算密集型验证例子
      • 3.小结
    • 三、锁与线程安全
    • 四、总结
    • 参考资料:

  • 因为GIL的存在,Python多线程是否鸡肋?
  • 既然已有GIL,是否Python编程不需要关注线程安全的问题?不需要使用锁?
  • 为什么Python进阶材料很少有讲解多线程?

一、GIL简介

首先我们看下Global Interpreter Lock(GIL)的官方介绍:

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

简而言之,因为CPython的内存管理不是线程安全的,所以需要加一个全局解释锁来保障Python内部对象是线程安全的。
GIL的存在导致Python多线程是不完整的多线程,Python社区内部对是否保留GIL一致激烈讨论,这里我们就不在累述。

二、Python多线程是否鸡肋

正如上节所说,Python的多线程是不完整的多线程。不过抛开具体应用场景谈“Python多线程是否鸡肋”就是耍流氓了!

1. 为什么需要多线程呢?

为什么需要多线程呢?总结一下,多线程多应用在如下场景:

  • 需要运行后台任务但不希望停止主线程的执行

    • 定期打印日志
    • 图形界面下,主循环需要等待事件
  • 分散任务负载

    • 高负载任务一般分计算密集型、IO密集型两类。

2. 计算密集型 vs. IO密集型

计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。

IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少。

计算密集型验证例子

Python作为一门脚本语言,本身执行效率极低,完全不适合计算密集型任务的开发。再加上GIL的存在,需要花费大量时间用在线程间的切换,其多线程性能甚至低于单线程。以下是一个验证例子:
顺序执行的单线程(single_thread.py)

#! /usr/bin/pythonfrom threading import Thread
import timedef my_counter():i = 0for _ in range(100000000):i = i + 1return Truedef main():thread_array = {}start_time = time.time()for tid in range(2):t = Thread(target=my_counter)t.start()t.join()end_time = time.time()print("Total time: {}".format(end_time - start_time))if __name__ == '__main__':main()

同时执行的两个并发线程(multi_thread.py)

#! /usr/bin/pythonfrom threading import Thread
import timedef my_counter():i = 0for _ in range(100000000):i = i + 1return Truedef main():thread_array = {}start_time = time.time()for tid in range(2):t = Thread(target=my_counter)t.start()thread_array[tid] = tfor i in range(2):thread_array[i].join()end_time = time.time()print("Total time: {}".format(end_time - start_time))if __name__ == '__main__':main()

多线程执行更慢了!

经过大量测试,Python多线程下一般最多只能占用1.5~2核,完全无法充分利用CPU资源。

3.小结

在低计算场景(普通后台任务、IO密集型任务)下,Python多线程还是有一点用武之地。但是计算密集型任务的话,Python多线程是真鸡肋,甚至会严重拖后腿。

三、锁与线程安全

既然有GIL这个语言级的锁,那我们是不是可以不关注锁与线程安全,直接起飞了?

且看下面这个例子

#! /usr/bin/pythonimport threadingi = 0def test():global ifor x in range(100000):i += 1threads = [threading.Thread(target=test) for t in range(10)]
for t in threads:t.start()for t in threads:t.join()assert i == 100000, i

显然失败了。因为高级语言的一条语句执行时都是分为若干条执行码,即使一个简单的计算:i += 1,也是分为4个执行码。

  • load i
  • load 1
  • add
  • store it back to i

Python解释器默认每100个操作码切换一个活动线程(通过从一个线程释放GIL以便另一个线程可以使用)。当100个操作码切换时,就会出现争抢,从而出现线程不安全的情况。此时就需要我们加一个简单的锁。

#!/usr/bin/python
import threading
i = 0
i_lock = threading.Lock()def test():global ii_lock.acquire()try:for x in range(100000):i += 1finally:i_lock.release()threads = [threading.Thread(target=test) for t in range(10)]
for t in threads:t.start()for t in threads:t.join()assert i == 100000, i

四、总结

相比Java那种天生面向多线程的语言不同,Python本身多线程就是不太完善的多线程。GIL的存在导致多线程CPU利用效率甚至低于单线程,却仍然要面对锁与线程安全的问题。同时Python语言又不像Java自带多种线程安全的数据类型,增加了多线程编程的复杂度,所以很少有资料大篇幅讲解Python多线程。
正如《Python高手之路》所言: (Python)处理好多线程是很难的,其复杂程度意味着与其他方式(异步事件\多进程)相比它是bug的更大来源,而且考虑到通常能够获取的好处很少,所以最好不要在多线程上浪费太多精力。

参考资料:

python中的GIL详解
is-the-operator-thread-safe-in-python
《Python高手之路》(《The Hacker’s Guide to Python》)

这篇关于【Python】谈谈Python多线程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1117602

相关文章

Python中模块graphviz使用入门

《Python中模块graphviz使用入门》graphviz是一个用于创建和操作图形的Python库,本文主要介绍了Python中模块graphviz使用入门,具有一定的参考价值,感兴趣的可以了解一... 目录1.安装2. 基本用法2.1 输出图像格式2.2 图像style设置2.3 属性2.4 子图和聚

Python使用Matplotlib绘制3D曲面图详解

《Python使用Matplotlib绘制3D曲面图详解》:本文主要介绍Python使用Matplotlib绘制3D曲面图,在Python中,使用Matplotlib库绘制3D曲面图可以通过mpl... 目录准备工作绘制简单的 3D 曲面图绘制 3D 曲面图添加线框和透明度控制图形视角Matplotlib

一文教你Python如何快速精准抓取网页数据

《一文教你Python如何快速精准抓取网页数据》这篇文章主要为大家详细介绍了如何利用Python实现快速精准抓取网页数据,文中的示例代码简洁易懂,具有一定的借鉴价值,有需要的小伙伴可以了解下... 目录1. 准备工作2. 基础爬虫实现3. 高级功能扩展3.1 抓取文章详情3.2 保存数据到文件4. 完整示例

使用Python实现IP地址和端口状态检测与监控

《使用Python实现IP地址和端口状态检测与监控》在网络运维和服务器管理中,IP地址和端口的可用性监控是保障业务连续性的基础需求,本文将带你用Python从零打造一个高可用IP监控系统,感兴趣的小伙... 目录概述:为什么需要IP监控系统使用步骤说明1. 环境准备2. 系统部署3. 核心功能配置系统效果展

基于Python打造一个智能单词管理神器

《基于Python打造一个智能单词管理神器》这篇文章主要为大家详细介绍了如何使用Python打造一个智能单词管理神器,从查询到导出的一站式解决,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 项目概述:为什么需要这个工具2. 环境搭建与快速入门2.1 环境要求2.2 首次运行配置3. 核心功能使用指

Python实现微信自动锁定工具

《Python实现微信自动锁定工具》在数字化办公时代,微信已成为职场沟通的重要工具,但临时离开时忘记锁屏可能导致敏感信息泄露,下面我们就来看看如何使用Python打造一个微信自动锁定工具吧... 目录引言:当微信隐私遇到自动化守护效果展示核心功能全景图技术亮点深度解析1. 无操作检测引擎2. 微信路径智能获

Python中pywin32 常用窗口操作的实现

《Python中pywin32常用窗口操作的实现》本文主要介绍了Python中pywin32常用窗口操作的实现,pywin32主要的作用是供Python开发者快速调用WindowsAPI的一个... 目录获取窗口句柄获取最前端窗口句柄获取指定坐标处的窗口根据窗口的完整标题匹配获取句柄根据窗口的类别匹配获取句

利用Python打造一个Excel记账模板

《利用Python打造一个Excel记账模板》这篇文章主要为大家详细介绍了如何使用Python打造一个超实用的Excel记账模板,可以帮助大家高效管理财务,迈向财富自由之路,感兴趣的小伙伴快跟随小编一... 目录设置预算百分比超支标红预警记账模板功能介绍基础记账预算管理可视化分析摸鱼时间理财法碎片时间利用财

Python中的Walrus运算符分析示例详解

《Python中的Walrus运算符分析示例详解》Python中的Walrus运算符(:=)是Python3.8引入的一个新特性,允许在表达式中同时赋值和返回值,它的核心作用是减少重复计算,提升代码简... 目录1. 在循环中避免重复计算2. 在条件判断中同时赋值变量3. 在列表推导式或字典推导式中简化逻辑

python处理带有时区的日期和时间数据

《python处理带有时区的日期和时间数据》这篇文章主要为大家详细介绍了如何在Python中使用pytz库处理时区信息,包括获取当前UTC时间,转换为特定时区等,有需要的小伙伴可以参考一下... 目录时区基本信息python datetime使用timezonepandas处理时区数据知识延展时区基本信息