Python 中用线程执行阻塞式 I/O,不做并行计算

2024-09-01 03:36

本文主要是介绍Python 中用线程执行阻塞式 I/O,不做并行计算,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Python 中用线程执行阻塞式 I/O,不做并行计算

尽管 Python 也支持多线程,但这些线程受 GIL(global interpreter lock,全局解释器锁) 约束,所以每次或许只能有一条线程向前推进,而无法实现多头并进。既然有这么多限制,那 Python 还支持多线程干什么?

首先,这种机制让我们很容易就能实现出一种效果,也就是令人感觉程序似乎能在同一时间做许多件事。这样的效果采用手工方式很难编写​,而通过线程来实现,则可以让 Python 自动替我们把这些问题处理好,让多项任务能够并发地执行。

其次,可以通过 Python 的多线程机制处理阻塞式的 I/O 任务,因为线程在执行某些系统调用的过程中会发生阻塞,假如只支持一条线程,那么整个程序就会卡在这里不动。Python 程序需要通过系统调用与外部环境交互,其中有一些调用属于阻塞式的 I/O 操作,例如读取文件、写入文件、联网以及与显示器等设备交互。多线程机制可以让程序中的其他线程继续执行各自的工作,只有发起调用请求的那条线程才需要卡在那里等待操作系统给出结果。

1.看一看用线程做并行计算。

例如,要用 Python 程序执行一批计算量很大的任务。以最原始的办法实现这样一个因数分解算法,以模拟这种任务。

def factorize(number):for i in range(1, number + 1):if number % i == 0:yield i

试着分解几个数。如果必须把上一个数分解完才能开始分解下一个数,那么程序花的时间就比较长。

import timedef factorize(number):for i in range(1, number + 1):if number % i == 0:yield inumbers = [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')# >>>
# Took 0.266 seconds

让每条线程各自分解一个数或许可以把计算机中的多个 CPU 核心充分利用起来。这种想法对于其他语言来说,可能有些道理,但是在 Python 中效果如何呢?试着定义这样一种 Python 线程,让它完成与刚才相同的因数分解任务。

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)

最后,等待所有线程执行完毕。

for thread in threads:thread.join()end = time.time()
delta = end - start
print(f'Took {delta:.3f} seconds')# >>>
# Took 0.277 seconds

奇怪的是,这样做竟然比一个一个去分解还要慢,没有把多核心的优势发挥出来。理论上像这样令 4 条线程分别执行各自的任务,应该比一条线程连续执行 4 份任务要快得多。这是因为,这种多线程的程序在标准的 CPython 解释器之中会受 GIL 牵制,例如 CPython 要通过 GIL 防止这些线程争抢全局锁,而且要花一些时间来协调。

2.再来看一看用线程执行阻塞式 I/O。

例如,要通过串口(serial port)向遥控直升机发送信号。以 select 这个系统调用来模拟这项操作。通过 select 函数让操作系统阻塞 0.1 秒,然后把控制权返还给本程序,这样就模拟出了使用串口通信的效果。

import select
import socketdef 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')# >>>
# Took 0.541 seconds

这样写有个问题,就是程序在执行 slow_systemcall 函数的过程中会彻底卡住,因为它的主线程会阻塞在 select 这里。在实际的程序里面,这是个相当糟糕的现象,因为向直升机发送信号的同时,还需要计算接下来的移动方式,不然直升机就会坠毁。所以,如果在执行阻塞式 I/O 的过程中还需要做计算,那么应该把系统调用放到另一条线程执行。下面把 slow_systemcall 放在单独的线程里调用,这样能同时触发多项操作,于是可以在与多个串口(乃至多个直升机)通信的同时,在主线程里做其他必要的运算。

start = time.time()
threads = []
for _ in range(5):thread = Thread(target=slow_systemcall)thread.start()threads.append(thread)

将 5 条线程启动之后,主线程可以先去计算直升机接下来的移动方式,然后再去等待那 5 条线程把各自的系统调用执行完毕。

def compute_helicopter_location(index):...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')# >>>
# Took 0.109 seconds

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

用多线程处理阻塞式 I/O 是最简单的,多条 Python 线程可以并行地执行多个系统调用,这样就能让程序在执行阻塞式的 I/O 任务时,继续做其他运算。

这篇关于Python 中用线程执行阻塞式 I/O,不做并行计算的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

如何通过Python实现一个消息队列

《如何通过Python实现一个消息队列》这篇文章主要为大家详细介绍了如何通过Python实现一个简单的消息队列,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录如何通过 python 实现消息队列如何把 http 请求放在队列中执行1. 使用 queue.Queue 和 reque

Python如何实现PDF隐私信息检测

《Python如何实现PDF隐私信息检测》随着越来越多的个人信息以电子形式存储和传输,确保这些信息的安全至关重要,本文将介绍如何使用Python检测PDF文件中的隐私信息,需要的可以参考下... 目录项目背景技术栈代码解析功能说明运行结php果在当今,数据隐私保护变得尤为重要。随着越来越多的个人信息以电子形

使用Python快速实现链接转word文档

《使用Python快速实现链接转word文档》这篇文章主要为大家详细介绍了如何使用Python快速实现链接转word文档功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 演示代码展示from newspaper import Articlefrom docx import

Python Jupyter Notebook导包报错问题及解决

《PythonJupyterNotebook导包报错问题及解决》在conda环境中安装包后,JupyterNotebook导入时出现ImportError,可能是由于包版本不对应或版本太高,解决方... 目录问题解决方法重新安装Jupyter NoteBook 更改Kernel总结问题在conda上安装了

Python如何计算两个不同类型列表的相似度

《Python如何计算两个不同类型列表的相似度》在编程中,经常需要比较两个列表的相似度,尤其是当这两个列表包含不同类型的元素时,下面小编就来讲讲如何使用Python计算两个不同类型列表的相似度吧... 目录摘要引言数字类型相似度欧几里得距离曼哈顿距离字符串类型相似度Levenshtein距离Jaccard相

在MySQL执行UPDATE语句时遇到的错误1175的解决方案

《在MySQL执行UPDATE语句时遇到的错误1175的解决方案》MySQL安全更新模式(SafeUpdateMode)限制了UPDATE和DELETE操作,要求使用WHERE子句时必须基于主键或索引... mysql 中遇到的 Error Code: 1175 是由于启用了 安全更新模式(Safe Upd

Python安装时常见报错以及解决方案

《Python安装时常见报错以及解决方案》:本文主要介绍在安装Python、配置环境变量、使用pip以及运行Python脚本时常见的错误及其解决方案,文中介绍的非常详细,需要的朋友可以参考下... 目录一、安装 python 时常见报错及解决方案(一)安装包下载失败(二)权限不足二、配置环境变量时常见报错及

Python中顺序结构和循环结构示例代码

《Python中顺序结构和循环结构示例代码》:本文主要介绍Python中的条件语句和循环语句,条件语句用于根据条件执行不同的代码块,循环语句用于重复执行一段代码,文章还详细说明了range函数的使... 目录一、条件语句(1)条件语句的定义(2)条件语句的语法(a)单分支 if(b)双分支 if-else(