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。
			break
	blanks = ' ' * 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 result
	
def 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 i

numbers = [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 factors

start = 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 = number
 
    def 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.mfbz.cn/a/226893.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ngnix和Apache配置SSL证书

本文主要介绍Ngnix和Apache配置SSL证书的方法 目录 SSL证书SSL证书的作用Ngnix简介Ngnix配置SSL证书Apache简介Apache配置SSL证书 SSL证书 SSL证书是一种数字证书,用于加密在网络上发送的数据并保护敏感信息的安全性。SSL代表“安全套接字层”,它是一种…

12.7 作业

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

Makefile语法

一、Makefile规则格式 Makefile 里面是由一系列的规则组成的,这些规则格式如下: 目标…... : 依赖文件集合…… 命令 1 命令 2 ……参考上一节gcc编译器与Makefile入门参考这条规则 1 main: main.o input.o calcu.o2 gcc -o main main.o input.o c…

采样率越高噪声越大?

ADC采样率指的是模拟到数字转换器(ADC)对模拟信号进行采样的速率。在数字信号处理系统中,模拟信号首先通过ADC转换为数字形式,以便计算机或其他数字设备能够处理它们。 ADC采样率通常以每秒采样的次数来表示,单位为赫…

详解http请求头,响应头以及在实际开发中

HTTP (Hypertext Transfer Protocol) 协议是一种用于传输超文本的标准协议,它是 Web 通信的基础。HTTP 协议是无状态的,即每次请求是相互独立的,服务器不会记住上一次请求的信息。HTTP 协议采用客户端-服务器模式,客户端发起请求&…

添加新公司代码的配置步骤-Part2

原文地址:配置公司代码 概述 在第一部分中,我讨论并列出了在 SAP 中构建新公司代码时企业结构部分所需的任务。在这篇博客中,我将列出并讨论 FI 模块中需要配置的内容。您还记得本主题涵盖六个部分。 企业结构 - 第 1 部分 FI 配置– 第 2…

2023新优化应用:RIME-CNN-LSTM-Attention超前24步多变量回归预测算法

程序平台:适用于MATLAB 2023版及以上版本。 霜冰优化算法是2023年发表于SCI、中科院二区Top期刊《Neurocomputing》上的新优化算法,现如今还未有RIME优化算法应用文献哦。RIME主要对霜冰的形成过程进行模拟,将其巧妙地应用于算法搜索领域。 …

Android String.xml 设置加粗字体/修改字体颜色/动态设置修改文案

之前经常使用Spannable 这次主要在String.xml使用&#xff1a;<![CDATA[和]]> 效果&#xff1a; <resources><string name"str_bianse"><![CDATA[变色 <font color"#ff0000">曲项向天歌</font> 白毛浮绿水]]></st…

[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|…

C++新经典模板与泛型编程:用成员函数重载实现is_base_of

用成员函数重载实现is_base_of std::is_base_of是一个C 11标准中用于判断某个类是否是另一个类父类的类模板。 #include "killCmake.h"#include<string>using namespace std;class A { };class B : public A { public:B(int x): x_(x){} private:int x_; };/…

低代码——“平衡饮食”才是王道

文章目录 一、低代码的概念二、低代码的优点2.1. 高效率与快速开发2.2. 降低技术门槛2.3. 适用于快速迭代与原型开发 三、低代码的缺点3.1. 定制性不足3.2. 深度不足3.3. 可能导致技术债务 四、低代码开发的未来4.1. 深度定制化4.2. 智能化 五、低代码会替代传统编程吗&#xf…

力扣每日一题day30[226. 翻转二叉树]

给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1]示例 2&#xff1a; 输入&#xff1a;root [2,1,3] 输出&#xff1a;[2,3,1]示例 3&#…

Abaqus基础教程--胶合失效仿真

胶合是电子行业中常见的连接方式&#xff0c;abaqus中常用cohesive单元或者cohesive接触两种方法进行胶合失效仿真&#xff0c;这两种方式操作方法有所差别&#xff0c;但结果一般大同小异。 本例模型比较简单&#xff0c;建模过程从略&#xff0c;使用静态分析&#xff0c;使…

月薪6W!美团、网易等大厂急招HarmonyOS开发!

近期&#xff0c;多家互联网公司发布了多个和鸿蒙系统有关的岗位。 不仅如此&#xff0c;还与Windows等主流老牌操作系统并列&#xff0c;并且排在首位介绍。 此外&#xff0c;今日头条招聘Android开发工程师也提及岗位需要“负责今日头条 Android、鸿蒙系统等新技术方向调研…

代码随想录算法训练营第三十七天|1049. 最后一块石头的重量 II ,494. 目标和,474.一和零

1049. 最后一块石头的重量 II - 力扣&#xff08;LeetCode&#xff09; 有一堆石头&#xff0c;用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将它们一起粉碎。假设石头的重量分别为 x 和 y&am…

前端Flex布局的常用属性及其应用场景

目录 学习目标&#xff1a; 学习内容&#xff1a; 什么是flex布局&#xff1f; 如何使用flex布局&#xff1f; 容器属性 项目属性 flex布局有哪些主要的属性&#xff1f; flex布局的优缺点是什么&#xff1f; 学习时间&#xff1a; 最后总结&#xff1a; 学习目标&am…

医院信息系统源码,采用JAVA编程,支持跨平台部署应用,满足一级综合医院(专科二级及以下医院500床)的日常业务应用

医院HIS系统源码&#xff0c;HIS系统全套源码&#xff0c;支持电子病历4级&#xff0c;自主版权 his医院信息系统内设门诊/住院医生工作站、门诊/住院护士工作站。各工作站主要功能依据职能要求进行研发。如医生工作站主要功能为编辑电子病历、打印、处理医嘱&#xff1b;护士工…

虾皮关键词工具:优化您的Shopee商品曝光度和搜索排名

在Shopee平台上&#xff0c;关键词工具对于提高商品曝光度和搜索排名非常重要。本文将向您介绍一些值得推荐的关键词工具&#xff0c;这些工具可以帮助您找到合适的关键词以优化您的商品列表&#xff0c;并提高搜索排名和曝光度。 先给大家推荐一款shopee知虾数据运营工具知虾免…

读者和写者问题

它可以解决的问题&#xff1a; 可以支持多个读者访问&#xff0c;通过count计数 来实现多个读者访问的时候是互斥的&#xff0c;不会出现不符合进程同步的问题&#xff1a;设置mutex互斥锁&#xff0c;保证count或count--和if Pv(mutex)是一气呵成的 读写公平&#xff0c;通过…

软件工程之UML建模

从公众号转载&#xff0c;关注微信公众号掌握更多技术动态 --------------------------------------------------------------- 一、建模基础 1.建模的底层逻辑 用一个公式表达建模的底层逻辑&#xff1a;建模 图形 逻辑 现实的抽象&#xff0c;用一句概括即是用图形逻辑…
最新文章