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.rhkb.cn/news/212524.html

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

相关文章

如何加快网络攻击发现速度

网络攻击可能会摧毁受害者。例如,米高梅度假村 (MGM Resorts) 预计将因 9 月份的网络攻击而遭受 1 亿美元的损失。 鲜为人知的是,在许多情况下,借助网络攻击发现可以预防网络攻击或将其消灭在萌芽状态。 威胁行为者变得越来越复杂&#xff…

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

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

文件同步及实现简单监控

1. 软件简介 rsync rsync 是一款开源的、快速的、多功能的、可实现全量及增量的本地或远程 数据同步备份的优秀工具。在同步备份数据时,默认情况下,Rsync 通过其 独特的“quick check”算法,它仅同步大小或者最后修改时间发生变化的文 件或…

Linux_CentOS_7.9配置oracle sqlplus、rman实现上下按键切换历史命令等便捷效率功能之简易记录

配置oracle sqlplus以及rman可以上下按键切换历史命令等便捷效率功能 设置前提是已经yum安装了rlwrap软件具体软件下载及配置参考文章http://t.csdnimg.cn/iXuVK su - oracleVim .bash_profile ## 文件中增加如下的别名设置 ---------------- alias sqlplusrlwrap sqlplus…

微信小程序uni.chooseImage()无效解决方案

Bug场景: 微信小程序在上传图片时可以通过 uni.chooseImage()方案进行上传,这里不再赘述具体参数。一直项目都可以正常使用,突然有一天发现无法使用该方法,于是查了一下,发现是用户隐私协议问题。故记录一下解决方案。…

自然语言处理基础知识 学习

参考:OpenBMB - 让大模型飞入千家万户 【清华NLP】刘知远团队大模型公开课全网首发|带你从入门到实战_哔哩哔哩_bilibili 图灵测试:imitation Game 模仿游戏 Part of speech tagging 词性标注 Named entity recognition : 命名…

Java se的语言特征之封装

目录 封装的概念常见的一些包静态成员变量代码块 封装的概念 可以理解为套壳屏蔽细节,将数据和操作数据的方法进行有机的结合,隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互 从语法的层面来理解就是,被private修饰的成员变或者成员方法,只能在当前类中使用,但是可以…

LeetCode 每日一题 Day 6(DFS+BFS)

1466. 重新规划路线 n 座城市,从 0 到 n-1 编号,其间共有 n-1 条路线。因此,要想在两座不同城市之间旅行只有唯一一条路线可供选择(路线网形成一颗树)。去年,交通运输部决定重新规划路线,以改变…

机器学习(2)回归

0.前提 上一期,我们简单的介绍了一些有关机器学习的内容。学习机器学习的最终目的是为了服务我未来的毕设选择之一——智能小车,所以其实大家完全可以根据自己的需求来学习这门课,我做完另一辆小车后打算花点时间去进行一次徒步行&#xff0…

【Python】Faker库详解:创建测试数据轻而易举

Python Faker库详解:创建测试数据轻而易举 在软件开发和测试过程中,通常需要大量的测试数据来模拟真实环境。Python的Faker库为开发者提供了一个方便、灵活且强大的工具,用于生成各种虚构数据。本文将深入介绍Faker库,演示其基本…

[BJDCTF2020]EzPHP 许多的特性

这道题可以学到很多东西 静下心来慢慢通过本地知道是干嘛用的就可以学会了 BJDctf2020 Ezphp_[bjdctf2020]ezphp-CSDN博客 这里开始 一部分一部分看 $_SERVER[QUERY_SRING]的漏洞 if($_SERVER) { if (preg_match(/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|…

PHP使用mkcert本地开发生成HTTPS证书 PhpEnv集成环境

PHP使用mkcert本地开发生成HTTPS证书 PhpEnv集成环境 前言一、介绍 mkcert二、安装/使用 mkcert1. 安装2. 使用 总结 前言 本地开发时有些功能只有在 https 证书的情况下才能使用, 例如一些 Web API 一、介绍 mkcert Github地址 mkcert 是一个制作本地可信开发证书的简单工具。…

Vue 静态渲染 v-pre

v-pre 指令&#xff1a;用于阻止 Vue 解析这个标签&#xff0c;直接渲染到页面中。 语法格式&#xff1a; <div v-pre> {{ 数据 }} </div> 基础使用&#xff1a; <template><h3>静态渲染 v-pre</h3><p v-pre>静态渲染&#xff1a;{{ n…

Java (JDK 21) 调用 OpenCV (4.8.0)

Java 调用 OpenCV 一.OpenCV 下载和安装二.创建 Java Maven 项目三.其他测试 一.OpenCV 下载和安装 Open CV 官网 可以下载编译好的包&#xff0c;也可以下载源码自行编译 双击安装 opencv-4.8.0-windows.exe 默认为当前目录 安装即解压缩 根据系统位数选择 将 x64 目录下 op…

Elasticsearch:评估 RAG - 指标之旅

作者&#xff1a;Quentin Herreros&#xff0c;Thomas Veasey&#xff0c;Thanos Papaoikonomou 2020年&#xff0c;Meta发表了一篇题为 “知识密集型NLP任务的检索增强生成” 的论文。 本文介绍了一种通过利用外部数据库将语言模型 (LLM) 知识扩展到初始训练数据之外的方法。 …

css 元素前后添加图标(::before 和 ::after 的妙用)

<template><div class"container"><div class"label">猜你喜欢</div></div> </template><style lang"scss" scoped> .label {display: flex;&::before,&::after {content: "";widt…

Matlab使用基础

基本命令 clear all %清除Workspace中的所有变量 clc %清除Command Window中的所有命令 %和%%是注释 whos%显示当前内存中的变量信息基础函数 abs()%取绝对值 char(65)%将ASCII码数值变成字符 num2str(65)%将里面的内容变成字符串 length()%字符串长度&#xff0c;不把/0的长…

文本润色工具有哪些,高质量的文本润色软件

在当今信息过载的时代&#xff0c;文本的重要性愈发凸显。即便是最精心构思的文章&#xff0c;若未经过仔细的润色&#xff0c;也难以达到最佳的表达效果。本文将专心分享文本润色工具的种类。 文本润色工具的种类 文本润色工具根据其功能和应用范围可以分为多个种类&#xff…

Android 13 Settings蓝牙列表卡顿问题排查及优化过程

一.背景 此问题是蓝牙列表界面息屏后再点击亮屏蓝牙界面卡住,划不动也不能返回,在人多的时候(附近开启的蓝牙设备过多的时候)会卡住大概四五秒才能滑动. 优化前效果见资源: 二.查找耗时点 根据Android Studio的Profiler工具进行排查,查找主线程时间线比较长的方法,如下:…

12.7 作业

1&#xff0c; #include "widget1.h"Widget1::Widget1(QWidget *parent): QWidget(parent) {//界面设置//修改界面大小this->resize(810,600);//固定界面大小this->setFixedSize(800,600);//修改界面的标题this->setWindowTitle("杰哥和阿伟专场"…