Python中的并发编程(2)线程的实现

Python中线程的实现

1. 线程

在Python中,threading 库提供了线程的接口。我们通过threading 中提供的接口创建、启动、同步线程。

例1. 使用线程旋转指针

想象一个场景:程序执行了一个耗时较长的操作,如复制一个大文件,我们希望这个过程中程序显示一个动画,表示程序正常运行没有卡死。

简化一下:启动一个函数,执行 3 秒。在这3秒内,在终端持续显示指针旋转的动画。下面用线程来实现这个操作。

注:本例代码主要来自《流畅的Python》(第二版) 19.4.1

首先我们定义旋转函数spin和阻塞函数slow
spin函数每隔0.1s依次打印\|/-,看起来就像是指针转动:
1

import itertools
import time
def spin(msg: str) -> None:  for char in itertools.cycle(r'\|/-'): status = f'\r{char} {msg}' print(status, end='', flush=True)time.sleep(0.1)blanks = ' ' * len(status)print(f'\r{blanks}\r', end='')if __name__ == '__main__':spin("thinking...")

slow函数用来模拟一个耗时的操作。这里我们直接调用time.sleep(3) 等待3秒,然后返回一个结果。

# 阻塞3秒,并返回42
def slow() -> int:time.sleep(3) return 42

调用time.sleep() 阻塞所在的线程,但是释放 GIL,其他 Python 线程可以继续运行。

现在,我们要用线程实现并发。看起来就像是slowspin同时进行。
下面对spin函数做了一些修改,通过threading.Event信号量来同步线程。

import itertools
import time
from threading import Thread, Event# 旋转
def spin(msg: str, done: Event) -> None:  # done用于同步线程for char in itertools.cycle(r'\|/-'): status = f'\r{char} {msg}' print(status, end='', flush=True)if done.wait(.1): #等待/阻塞 。除非有其他线程set了这个事件,则返回True;或者经过指定的时间(0.1s)后,返回 False。breakblanks = ' ' * len(status)print(f'\r{blanks}\r', end='')# 阻塞3秒,并返回42
def slow() -> int:time.sleep(3) return 42

使用线程来并发执行两个函数。
下面我们只手动启动了一个spinner线程,因为程序本身就有一个主线程。

def supervisor() -> int: done = Event()  # 信号量,用于线程同步spinner = Thread(target=spin, args=('thinking!', done)) # 使用Thread创建线程实例spinner。print(f'spinner object: {spinner}') spinner.start() # 启动spinner线程result = slow()  # 调用slow,阻塞 main 线程。同时,次线程spinner运行旋转指针动画done.set() # 设置done为真,唤醒等待done的线程。结束spinner中的循环。spinner.join() # 等待spinner 线程结束。-貌似这里加不加都不影响。return resultdef main() -> None:result = supervisor() print(f'Answer: {result}')if __name__ == '__main__':main()

在这里插入图片描述

程序的执行顺序,主要步骤都发生在supervisor函数中,我们跳过main从supervisor开始看。
由于GIL的存在,同一时刻只有一个线程在执行。所以下面是一个顺序执行的过程。
执行过程大致如下:
在这里插入图片描述

主线程:创建spinner线程,启动spinner线程
spinner线程:输出字符,然后遇到done.wait(.1) 阻塞自己。
主线程:调用slow函数,遇到time.sleep(3) 阻塞
spinner线程:done.wait(.1) 超过了0.1秒返回False,继续输出字符。重复进行阻塞0.1秒、输出字符。
3秒后…
主线程:slow执行完毕,返回结果42。主线程继续执行done.set(),这会唤醒等待done的线程spinner。
spinner线程:运行到done.wait(.1),由于主线程执行了done.set()使得这里的结果为True,所以执行break,结束循环。执行循环下面的print语句后spinner线程结束。
主线程:返回结果。

例2.计算因子

第二个例子我们看一个(失败的)并行计算的例子:
我们希望用n个线程并行计算n个数各自的因子。

注:本例代码来自《Effective Python》(第二版) 第53章

基准方法
逐个计算。

import time# 计算number的因子
def factorize(number):for i in range(1, number + 1):if number % i == 0:yield inumbers = [2139079, 1214759, 1516637, 1852285, 14256346, 12456533]
start = time.time()for number in numbers:list(factorize(number))end = time.time()
delta = end - start
print(f'串行方法花费了 {delta:.3f} 秒')

多线程方式
可以像例1中使用Thread函数实现线程:

def get_factor(number):factors = list(factorize(number))return factorsstart = time.time()
threads = []
for number in numbers:thread = Thread(target=get_factor, args=(number,))thread.start() # 启动threads.append(thread)# 等待所有线程完成
for thread in threads:thread.join() # 等待完成end = time.time()
delta = end - start
print(f'Thread方法花费了 {delta:.3f} 秒')

实现线程的另一种方式是继承Thread类并实现run方法:

from threading import Thread# 继承Thread,需要实现run方法,在run方法中执行要做的事情
class 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'Thread方法花费了 {delta:.3f} 秒')

运行结果:
2

你会发现这个多线程的版本并没有变快,这并不意外。
介绍线程时说过,因为GIL的存在,多线程无法同时执行,甚至因为创建和切换线程产生额外的开销导致耗时增加。

小结
在GIL的限制下,Python线程对于并行计算没有用处,但是对于等待(IO、网络、后台任务)是有用处的。下一节我们会看一些Python线程的实际案例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/206049.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2022年第十一届数学建模国际赛小美赛D题野生动物贸易是否应长期禁止解题全过程文档及程序

2022年第十一届数学建模国际赛小美赛 D题 野生动物贸易是否应长期禁止 原题再现: 野生动物市场被怀疑是此次疫情和2002年SARS疫情的源头,食用野生肉类被认为是非洲埃博拉病毒的一个来源。在冠状病毒爆发后,中国最高立法机构永久性地加强了野…

【git教程】

目录 git与SVN的区别:集中式与分布式的区别Windows上安装Git创建版本库/仓库(repository)将文件添加到repository报错处理 查看仓库的状态版本回退工作区和暂存区管理和修改撤销修改删除文件远程仓库添加远程仓库警告解除本地和远程的绑定关系…

一文3000字从0到1用Python进行gRPC接口测试!

gRPC 是一个高性能、通用的开源RPC框架,其由 Google 主要面向移动应用开发并基于HTTP/2 协议标准而设计,基于 ProtoBuf(Protocol Buffers) 序列化协议开发,且支持众多开发语言。 自gRPC推出以来,已经广泛应用于各种服务之中。在测…

AI助力智慧农业,基于SSD模型开发构建田间作物场景下庄稼作物、杂草检测识别系统

智慧农业随着数字化信息化浪潮的演变有了新的定义,在前面的系列博文中,我们从一些现实世界里面的所见所想所感进行了很多对应的实践,感兴趣的话可以自行移步阅读即可:《自建数据集,基于YOLOv7开发构建农田场景下杂草检…

【C语言快速学习基础篇】之二控制语句、循环语句、隐式转换

文章目录 一、控制语句1.1、for循环1.2、while循环1.3、注意:for循环和while循环使用上面等同1.4、do while循环1.4.1while条件成立时1.4.2、while条件不成立时 C语言介绍 C语言是一门面向过程的计算机编程语言,与C、C#、Java等面向对象编程语言有所不同…

“分割“安卓用户,对标iOS,鸿蒙崛起~

近期关于**“华为于明年推出不兼容安卓的鸿蒙版本”**的消息传出,引起了业界的热议关注。自从2019年8月,美国制裁下,华为不再能够获得谷歌安卓操作系统相关付费服务,如此情况下,华为“备胎”鸿蒙操作系统一夜转正。 华…

有效解决wordpress的502 Bad Gateway错误提示

摘要:最近有客户反映使用阿里云虚拟云主机,wordpress常提示502 Bad Gateway错误,网关错误是网站上遇到的常... wordpress的502 Bad Gateway错误如何修复? 第1步:偶发错误可尝试重新加载网站 偶尔出现流量突发爆增或是服…

Sql Server关于表的建立、修改、删除

表的创建: (1)在“对象资源管理器”面板中展开“数据库”节点,可以看到自己创建的数据库,比如Product。展开Product节点,右击“表”节点,在弹出的快捷菜单中选择“新建表”项,进入“…

打工人副业变现秘籍,某多/某手变现底层引擎-Stable Diffusion简介

Stable Diffusion是2022年发布的深度学习文本到图像生成模型,它主要用于根据文本的描述产生详细图像,尽管它也可以应用于其他任务,如

K-means算法通俗原理及Python与R语言的分别实现

K均值聚类方法是一种划分聚类方法,它是将数据分成互不相交的K类。K均值法先指定聚类数,目标是使每个数据到数据点所属聚类中心的总距离变异平方和最小,规定聚类中心时则是以该类数据点的平均值作为聚类中心。 01K均值法原理与步骤 对于有N个…

[HITCON 2017]SSRFme perl语言的 GET open file 造成rce

这里记录学习一下 perl的open缺陷 这里首先本地测试一下 发现这里使用open打开 的时候 如果通过管道符 就会实现命令执行 然后这里注意的是 perl 中的get 调用了 open的参数 所以其实我们可以通过管道符实现命令执行 然后这里如果file可控那么就继续可以实现命令执行 这里就…

JavaSE基础50题:12. 编写代码模拟三次密码输入的场景。

概述 编写代码模拟三次输入的场景,最多能输入三次密码,密码正确,提示 “登录成功” ,密码错误,可重新输入,最多输入三次,三次均错,则提示退出程序。 代码 import java.util.Scann…

Redission分布式锁原理初探

什么是分布式锁,为什么需要分布式锁 在多线程并发请求当中,为了保证我们的资源同一时刻只有一个线程进行操作(如商品超卖问题、购票系统等),我们通常要添加锁机制,如ReentrantLock,也就是可重入…

C# 使用FluentScheduler触发定时任务

写在前面 FluentScheduler是.Net平台下的一个自动任务调度组件,以前经常用的是Quarz.Net,相对而言FluentScheduler的定时配置更为直观,可直接用接口进行参数化设置,对Cron表达式有恐惧症的人来说简直就是福音,使用起来…

Linux——进程状态

我们都知道进程信息被放到了PCB(task_struct)中,可以理解为进程属性的集合。 PCB中包含了进程的ID,时间片,pc指针,所有的寄存器,进程状态、优先级、I/O状态信息等等...有兴趣的可以去看看源码&…

【计算机网络笔记】物理层——频带传输基础

系列文章目录 什么是计算机网络? 什么是网络协议? 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能(1)——速率、带宽、延迟 计算机网络性能(2)…

LAMP和分离式LNMP部署

目录 一.什么是LAMP? 二.安装LAMP 先安装apache,httpd网页服务: 接着安装mysql: 安装php: 创建论坛: 三.安装分布式LNMP: 先安装nginx: 到另一台主机安装php: …

整数二分的建模

当题目能够使用整数二分法建模时&#xff0c;主要有整数二分法思想进行判定&#xff0c;它的基本形式如下&#xff1a; while(left < right) {int ans;//记录答案 int mid left (right - left) / 2;//二分if(check(mid)){//检查条件&#xff0c;如果成立 ans mid;//记录…

Python实现的二叉树的先序、中序、后序遍历示例

一、先序、中序、后序遍历的次序&#xff1a; 创建好一棵二叉树后&#xff0c;可以按照一定的顺序对树中所有的元素进行遍历。按照先左后右&#xff0c;树 的遍历方法有三种&#xff1a;先序遍历、中序遍历和后序遍历。 其中&#xff0c;先序遍历的次序是&#xff1a;如果二叉…

用23种设计模式打造一个cocos creator的游戏框架----(一)生成器模式

1、模式标准 模式名称&#xff1a;生成器模式 模式分类&#xff1a;创建型 模式意图&#xff1a;将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 结构图&#xff1a; 适用于&#xff1a; 当创建复杂对象的算法应该独立于该对象的…