python并发与并行(二) ———— 用线程执行阻塞式IO,但不要用它做并行计算

2024-08-29 05:44

本文主要是介绍python并发与并行(二) ———— 用线程执行阻塞式IO,但不要用它做并行计算,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Python语言的标准实现叫作CPython,它分两步来运行Python程序。首先解析源代码文本,并将其编译成字节码(bytecode)。字节码是一种底层代码,可以把程序表示成8位的指令(从Python 3.6开始,这种底层代码实际上已经变成16位了,所以应该叫作wordcode才对,但基本原理依然相同)。然后,CPython采用基于栈的解释器来运行字节码。这种字节码解释器在执行Python程序的过程中,必须确保相关的状态不受干扰,所以CPython会用一种叫作全局解释器锁(global interpreter lock,GIL)的机制来保证这一点。

GIL实际上就是一种互斥锁(mutual-exclusion lock,mutex),用来防止CPython的状态在抢占式的多线程环境(preemptive multithreading)之中受到干扰,因为在这种环境下,一条线程有可能突然打断另一条线程抢占程序的控制权。如果这种抢占行为来得不是时候,那么解释器的状态(例如为垃圾回收工作而设立的引用计数等)就会遭到破坏。所以,CPython要通过GIL阻止这样的动作,以确保它自身以及它的那些C扩展模块能够正确地执行每一条字节码指令。
但是,GIL会产生一个很不好的影响。在C++与Java这样的语言里面,如果程序之中有多个线程能够分头执行任务,那么就可以把CPU的各个核心充分地利用起来。尽管Python也支持多线程,但这些线程受GIL约束,所以每次或许只能有一条线程向前推进,而无法实现多头并进。所以,想通过多线程做并行计算或是给程序提速的开发者,恐怕要失望了。

我们用一段计算量很大的任务来看一下python在用多线程执行计算密集型任务时的表现。


# 因数分解算法
def factorize(number):for i in range(1,number+1):if number %i==0:yield iimport timenumbers = [2139079, 1214759, 1516637, 1852285]
start = time.time()for number in numbers:list(factorize(number))end = time.time()
delta = end - start
print(f'Took {delta:.3f} seconds')from threading import Threadclass FactorizeThread(Thread):def __init__(self, number):super().__init__()self.number = numberdef run(self):self.factors = list(factorize(self.number))start = time.time()threads = []
for number in numbers:thread = FactorizeThread(number)thread.start()threads.append(thread)# thread.join()方法的作用是等待线程完成。当你启动一个线程后,这个线程会异步执行。如果你希望主线程(通常是执行thread.start()的线程)等待这个新线程完成其任务后再继续执行,你就需要调用thread.join()。
# 如果不调用thread.join(),主线程可能会在其他线程完成之前继续执行,这可能导致一些不可预测的行为或资源访问冲突,特别是当多个线程需要访问共享资源时。通过调用join(),你确保了主线程会等待每个工作线程完成其执行,从而实现线程间的同步。
for thread in threads:thread.join()end = time.time()
delta = end - start
print(f'Took {delta:.3f} seconds')

Output:

Took 0.179 seconds
Took 0.158 seconds

我们看结果,多线程并没有比单线程快很多。

下面我们看个IO密集型的任务。


import select
import socket
import time
from threading import Thread#select.select,这是一个系统调用,用于监视文件描述符集合的变化情况。具体来说,select 函数可以监视三种类型的文件描述符集合:
#可读集合(readfds):等待数据变得可读(例如,网络套接字上有数据可读)的文件描述符集合。
#可写集合(writefds):等待数据变得可写(例如,套接字缓冲区有足够的空间可以发送数据)的文件描述符集合。
#异常集合(exceptfds):等待异常情况(如带外数据到达)的文件描述符集合。
#select.select 函数的最后一个参数是一个超时值,表示 select 函数等待事件发生的最长时间。在这个例子中,超时值被设置为 0.1 秒,这意味着 select 会在 0.1 秒后超时,无论是否有事件发生。
def slow_systemcall():select.select([socket.socket()], [], [], 0.1)start = time.time()for _ in range(5):slow_systemcall()end = time.time()
delta = end - start
print(f'Took {delta:.3f} seconds')start = time.time()threads = []
for _ in range(5):thread = Thread(target=slow_systemcall)thread.start()threads.append(thread)# 用此函数来模拟在执行系统调用时,我们还同时在做其他的事情
def compute_helicopter_location(index):print('Running compute helicopter location ')for i in range(5):compute_helicopter_location(i)for thread in threads:thread.join()end = time.time()
delta = end - start
print(f'Took {delta:.3f} seconds')

Output:

Took 0.517 seconds
Running compute helicopter location 
Running compute helicopter location 
Running compute helicopter location 
Running compute helicopter location 
Running compute helicopter location 
Took 0.108 seconds

与依次执行系统调用的那种写法相比,这种写法的速度几乎能达到原来的5倍。这说明,尽管那5条线程依然受GIL制约,但它们所发起的系统调用是可以各自向前执行的。GIL只不过是让Python内部的代码无法平行推进而已,至于系统调用,则不会受到影响,因为Python线程在即将执行系统调用时,会释放GIL,待完成调用之后,才会重新获取它。

与依次执行系统调用的那种写法相比,这种写法的速度几乎能达到原来的5倍。这说明,尽管那5条线程依然受GIL制约,但它们所发起的系统调用是可以各自向前执行的。GIL只不过是让Python内部的代码无法平行推进而已,至于系统调用,则不会受到影响,因为Python线程在即将执行系统调用时,会释放GIL,待完成调用之后,才会重新获取它。

这篇关于python并发与并行(二) ———— 用线程执行阻塞式IO,但不要用它做并行计算的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

python常见环境管理工具超全解析

《python常见环境管理工具超全解析》在Python开发中,管理多个项目及其依赖项通常是一个挑战,下面:本文主要介绍python常见环境管理工具的相关资料,文中通过代码介绍的非常详细,需要的朋友... 目录1. conda2. pip3. uvuv 工具自动创建和管理环境的特点4. setup.py5.

Python常用命令提示符使用方法详解

《Python常用命令提示符使用方法详解》在学习python的过程中,我们需要用到命令提示符(CMD)进行环境的配置,:本文主要介绍Python常用命令提示符使用方法的相关资料,文中通过代码介绍的... 目录一、python环境基础命令【Windows】1、检查Python是否安装2、 查看Python的安

Python UV安装、升级、卸载详细步骤记录

《PythonUV安装、升级、卸载详细步骤记录》:本文主要介绍PythonUV安装、升级、卸载的详细步骤,uv是Astral推出的下一代Python包与项目管理器,主打单一可执行文件、极致性能... 目录安装检查升级设置自动补全卸载UV 命令总结 官方文档详见:https://docs.astral.sh/

Python并行处理实战之如何使用ProcessPoolExecutor加速计算

《Python并行处理实战之如何使用ProcessPoolExecutor加速计算》Python提供了多种并行处理的方式,其中concurrent.futures模块的ProcessPoolExecu... 目录简介完整代码示例代码解释1. 导入必要的模块2. 定义处理函数3. 主函数4. 生成数字列表5.

Python中help()和dir()函数的使用

《Python中help()和dir()函数的使用》我们经常需要查看某个对象(如模块、类、函数等)的属性和方法,Python提供了两个内置函数help()和dir(),它们可以帮助我们快速了解代... 目录1. 引言2. help() 函数2.1 作用2.2 使用方法2.3 示例(1) 查看内置函数的帮助(

Python虚拟环境与Conda使用指南分享

《Python虚拟环境与Conda使用指南分享》:本文主要介绍Python虚拟环境与Conda使用指南,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、python 虚拟环境概述1.1 什么是虚拟环境1.2 为什么需要虚拟环境二、Python 内置的虚拟环境工具

Python实例题之pygame开发打飞机游戏实例代码

《Python实例题之pygame开发打飞机游戏实例代码》对于python的学习者,能够写出一个飞机大战的程序代码,是不是感觉到非常的开心,:本文主要介绍Python实例题之pygame开发打飞机... 目录题目pygame-aircraft-game使用 Pygame 开发的打飞机游戏脚本代码解释初始化部

Python pip下载包及所有依赖到指定文件夹的步骤说明

《Pythonpip下载包及所有依赖到指定文件夹的步骤说明》为了方便开发和部署,我们常常需要将Python项目所依赖的第三方包导出到本地文件夹中,:本文主要介绍Pythonpip下载包及所有依... 目录步骤说明命令格式示例参数说明离线安装方法注意事项总结要使用pip下载包及其所有依赖到指定文件夹,请按照以

Python实现精准提取 PDF中的文本,表格与图片

《Python实现精准提取PDF中的文本,表格与图片》在实际的系统开发中,处理PDF文件不仅限于读取整页文本,还有提取文档中的表格数据,图片或特定区域的内容,下面我们来看看如何使用Python实... 目录安装 python 库提取 PDF 文本内容:获取整页文本与指定区域内容获取页面上的所有文本内容获取

基于Python实现一个Windows Tree命令工具

《基于Python实现一个WindowsTree命令工具》今天想要在Windows平台的CMD命令终端窗口中使用像Linux下的tree命令,打印一下目录结构层级树,然而还真有tree命令,但是发现... 目录引言实现代码使用说明可用选项示例用法功能特点添加到环境变量方法一:创建批处理文件并添加到PATH1